服务间的通信
前几篇讲述了SpringCloud的注册中心,讲解了怎么配置注册中心,已经将我们的服务注册,那么现在有一个问题:加入我们现在有两个服务User,Order,我User中需要调用Order中的接口,这个服务间的通讯该怎么实现。
前面讲过SpringCloud中各个服务之间的通讯使用的是HTTP协议传递数据。
那么我们怎么在java代码中发起HTTP请求呢?
RestTemplate:Spring提供给我们的HttpClient对象,这个对象可以想我们使用的浏览器一样给我们的后端发送请求。
我们来写个demo就以上面的User,Oder为例
1、首先我们现在一个父项目中,创建两个两个子项目,并配置成SpringBoot项目
2、配置Consul注册中心,配置如下
依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
|
yaml:以user为例
1 2 3 4 5 6 7 8 9
| server: port: 8888 spring: application: name: USERS cloud: consul: host: 127.0.0.1 port: 8500
|
3、因为只是模仿业务这里就简单的创建controller
OrderController
1 2 3 4 5 6 7 8 9 10 11 12
|
@RestController public class OrderController { @GetMapping("order") public String invokeOrder(){ Log.info("order demo>>>>>>>"); return "order 服务"; } }
|
UsersControlleer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@RestController public class UserController { @GetMapping("user") public String invokeUser(){ Log.info("user demo>>>>>"); RestTemplate restTemplate = new RestTemplate(); String forObject = restTemplate.getForObject("http:// 127.0.0.1:9999/order", String.class); Log.info("调用订单服务"+forObject); return "结果为:"+forObject; } }
|
可以看到成功访问。
存在的问题
1、调用服务的路径主机和服务器端口直接写死在url中无法实现服务集群时请求负载均衡
2、调用服务的请求路径写死在代码中,日后路径发生变化时不利于后续维护
如何解决
自定义负载均衡解决策略
简单来说就是将路径集放到一个集合中,然后随机从集合中得到一个,但还是无法实现服务健康检查,并且策略过于单一
所以推荐使用springcloud提供的组件ribbon解决负载均衡调用。
Ribbon
Spring-cloud-netflix-ribbon
是一个基于HTTP和TCP的客户端负载均衡工具,它基于NetFlix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。
Ribbon负载均衡原理
Ribbon的调用
首先我们发现,在上次我们引入的consul包中,他已经包含了ribbon的依赖,所以我们无需再次引用。
DiscoveryClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@RestController public class UserController { @Autowired private DiscoveryClient discoveryClient; @GetMapping("user") public String invokeUser(){ RestTemplate restTemplate = new RestTemplate();
List<ServiceInstance> orders = discoveryClient.getInstances("ORDERS"); String forObject = restTemplate.getForObject(orders.get(0).getUri() + "/order", String.class); return "结果为:"+forObject; } }
|
我们可以发现DiscoveryClient 方式,仅仅是根据服务的名称的得到了服务的列表,然后列表中获取一个地址,然后用RestTemplate发起请求访问,并没有做什么负载均衡处理。
LoadBalancerClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController public class UserController { @Autowired private DiscoveryClient discoveryClient; @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("user") public String invokeUser(){ RestTemplate restTemplate = new RestTemplate();
ServiceInstance orders = loadBalancerClient.choose("ORDERS"); String forObject = restTemplate.getForObject(orders.getUri() + "/order", String.class); return "结果为:"+forObject; } }
|
这个可以看出,有默认的负载均衡策略,这个策略就是轮询(比如A,B 一次A,一次B)。但是使用时需要每次现根据服务id获取一个负载均衡机器之后再通过RestTemplate调用服务。
@loadBalance+RestTemplate
这是比较常用的方式,首先上面代码中都出现了new关键字,我们都知道我们通常将这种对象注册到bean中,我们配置bean并且添加@LoadBalanced
1 2 3 4 5 6 7 8
| @Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
|
然后
1 2 3 4 5 6 7 8 9 10
| @RestController public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("user") public String invokeUser(){ String forObject = restTemplate.getForObject("http://ORDERS/order", String.class); return "结果为:"+forObject; } }
|
是不是发现这个url不同?
@loadBalance+RestTemplate的方式,就是会自动解析url,ORDERS是我们注册服务的名字,这样依旧具有负载均衡,依旧是轮询。
但是路径还是死的,还是不利于维护。
Ribbon负载均衡策略
- RoundRobinRule:轮询策略 按照顺序循环选择
- RandomRule:随机策略 随机选择
- AvailabilityFilteringRule:可用过滤策略
会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
- WeigthedResponseTimeRule:响应时间加权策略
根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够然后切换策略
先按照RoundRobinRule的策略获取服务,如果获取失败则在制定的时间内进行重试,获取可用服务。
会过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务