- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
- 配置
代码语言:javascript复制假设订单服务有两台,分别分8060、8061的两个服务
micro-order-service.ribbon.listOfServers=
http://localhost:8060/orders,
http://localhost:8061/orders
- 客户端负载均衡处理(user-service代码片段)
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/orders")
public String orders(){
// 通过LoadBalancerClient获取一个ServiceInstance实例
// 其中包含了host、port等信息
ServiceInstance serviceInstance = loadBalancerClient.choose("micro-order-service");
return restTemplate.getForObject(String.format("http://%s:%s/orders",
serviceInstance.getHost(), serviceInstance.getPort()), String.class);
}
- order-service
@Value("${server.port}")
private Integer port;
@GetMapping(value = "/orders")
public String getOrders() {
System.out.printf("curr service port: %s n", port);
System.out.println();
// 伪代码
return "{"
"订单编号: 12345678900000,"
"订单编号: 12345678900001,"
"}";
}
查看控制台会发现轮询打印了端口的信息
@LoadBalanced
代码语言:javascript复制修改user-service服务的代码如下,会发现带来同样的效果
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Bean
// 这里加上@LoadBalanced
@LoadBalanced
public RestTemplate newRestTemplate(RestTemplateBuilder builder){
return builder.build();
}
@GetMapping(value = "/orders")
public String orders(){
// 直接将host和port换成micro-order-service
return restTemplate.getForObject("http://micro-order-service/orders", String.class);
}
@LoadBalanced做了什么
@Qualifier
进入
@LoadBalanced
会发现当前类被@Qualifier标注;那么@Qualifier
的作用是什么
1.1. 小测试
代码语言:javascript复制 @Qualifier
@Bean("user1")
public User newUser1(){
return new User("张三");
}
@Bean("user2")
@Qualifier
public User newUser2(){
return new User("李四");
}
@Autowired
@Qualifier
private List<User> users = Collections.emptyList();
@GetMapping(value = "/users")
public List users(){
return users;
}
代码语言:javascript复制当我们访问的 /users的时候会获取张三、李四两个对象;当我们将张三的
@Qualifier
注解去掉之后,只会获得李四一个对象;由此可以知道@Qualifier
,可以作为一个标识,并且只能拿到同样标识了@Qualifier
注解的对象。同时@Qualifier还具有分组的作用
@Qualifier("g1")
@Bean
public User newTest(){
return new User("111");
}
@Qualifier("g1")
@Bean
public User newTest2(){
return new User("222");
}
@Qualifier("g2")
@Bean
public User newTest3(){
return new User("333");
}
@Qualifier("g2")
@Autowired
List<User> u1;
@Qualifier("g1")
@Autowired
List<User> u2;
@GetMapping(value = "/test")
public Map test(){
Map<String, List<User>> map = new HashMap<>();
map.put("g2",u1);
map.put("g1",u2);
return map;
}
LoadBalancerAutoConfiguration
按照正常的逻辑来讲,一个http请求肯定需要host,port才可以发送请求,那么很显然我们通过serviceId的方式也可以发送,说明在发送之前肯定是做了拦截操作,
LoadBalancerAutoConfiguration
在程序启动的时候会被加载。
- 通过调试不难发现这里会加载
LoadBalancerInterceptor
拦截
- 然后会将拦截器置入到
RestTemplate
中
发送请求
发送请求的时候我们继续调试会进入到
RestTemplate
的doExecute方法中
,大致如下所示:
- 这里有个重要的方法createRequest
继续跟踪会发现这里会去选择一个
RequestFactory
去创建请求, 有两个实现如下: org.springframework.http.client.support.HttpAccessor, org.springframework.http.client.support.InterceptingHttpAccessor
- InterceptingHttpAccessor
InterceptingHttpAccessor 是HttpAccessor的子类,InterceptingHttpAccessor一定会覆盖HttpAccessor的同名方法
- getRequestFactory
很显然这里的getInterceptors就是前面LoadBalancerAutoConfiguration中设置的。所以最后这里返回的一定是
InterceptingClientHttpRequestFactory
- 返回到createReuquest的地方
这里发现并没有我们要找的
InterceptingClientHttpRequestFactory
类,因此我们进入到抽象类中
InterceptingClientHttpRequestFactory
的createRequest
createRequest方法在当前抽象类中没有做处理,是在子类中处理的,其中有两个子类 org.springframework.http.client.InterceptingClientHttpRequestFactory org.springframework.http.client.BufferingClientHttpRequestFactory
- 创建InterceptingClientHttpRequest
逐步跟进最后会进入到
InterceptingClientHttpRequestFactory
类中然后创建的request对象显然是InterceptingClientHttpRequest
- 返回到RestTemplate的doExecute方法
这里会有个execute方法,很显然没有找到
InterceptingClientHttpRequest
类,所以我们继续进入到抽象类:org.springframework.http.client.AbstractClientHttpRequest
- AbstractBufferingClientHttpRequest#executeInternal
依然没有找到
InterceptingClientHttpRequest
,所以需要继续跟进到 org.springframework.http.client.AbstractBufferingClientHttpRequest#executeInternal类中,继续跟踪
- 千呼万唤使出来
突破层层包装以后终于找到了我们需要找到的
InterceptingClientHttpRequest
- InterceptingClientHttpRequest内部类InterceptingRequestExecution#execute
继续跟进,最后会进入到内部类InterceptingRequestExecution的execute方法.然后会判断是否有拦截器,答案是肯定的;而且通过前面
LoadBalancerAutoConfiguration
的介绍,最终一定会进入到:org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept
RibbonLoadBalancerClient
这里会进入到
RibbonLoadBalancerClient#execute
, 这里的serviceId即前面传入的。然后会选择一个负载均衡器。如下图示:
- 进入到getLoadBalancer
进入到getLoadBalancer方法逐步跟进最后会进入到 org.springframework.cloud.netflix.ribbon.SpringClientFactory#getInstance方法拿到负载均衡器及所有的实例并返回,这里会根据我们在配置文件中配置的内容: app-service: ribbon: listOfServers: http://localhost:8060,http://localhost:8061
getServer
进入到getServer这里会选择一个具体的实例。: ZoneAwareLoadBalancer是DynamicServerListLoadBalancer的子类所以会进入到子类方法中: com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer
ZoneAwareLoadBalancer
在
DynamicServerListLoadBalancer
中因为没有chooseServer
方法,所以会继续往上找到: com.netflix.loadbalancer.BaseLoadBalancer
注意这里的判断条件会判断 ZoneAwareNIWSDiscoveryLoadBalancer.enabled这个配置是否是false(默认为true),或者AvailableZones=1(考虑到跨机房部署),所以这里会执行if条件中的内容
- BaseLoadBalancer#chooseServer
这里会根据负债均衡规则获取实例
- ZoneAvoidanceRule
在
RibbonClientConfiguration
中默认配置的路由规则为ZoneAvoidanceRule
,因为不存在choose方法所以找到他的父类PredicateBasedRule
3. chooseRoundRobinAfterFiltering方法
根据方法名,或者方法上的注释不难发现最后还是采用的轮询算法。
- incrementAndGetModulo方法
关于IRule.
IRule
是制定负载均衡规则的接口,提供了诸如: 随机、轮询、最可用、响应时间等不同的规则,这里默认采用的是轮询。
关于IPing.
如上图所示如何知道节点是否可用就需要有一个健康检查机制,那么
IPing
提供了一系列的健康检查机制,甚至我们自己也可以实现自己的健康检查规则:
- RibbonClientConfiguration中定义了默认的规则
进入到com.netflix.loadbalancer.DummyPing内部会发现isAlive方法返回
true
,即为不做检查,默认都是存活的状态
- 自定义规则
官方文档指出通过如下方式配置即可实现自定义规则,从源码中也可以诡探一二
- RibbonClientConfiguration#ribbonPing
进入到:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration#ribbonPing,
服务发现和更新
- DynamicServerListLoadBalancer的构造方法 在调用的过程中,会进入到DynamicServerListLoadBalancer的构造方法中
DynamicServerListLoadBalancer(IClientConfig, IRule , IPing , ServerList<T> , ServerListFilter<T>, ServerListUpdater)
- restOfInit方法
在restOfInit方法中执行服务列表的更新动作,
enableAndInitLearnNewServersFeature
方法会去执行PollingServerListUpdater
的start方法,如下图所示:
- com.netflix.loadbalancer.PollingServerListUpdater
最终会进入到com.netflix.loadbalancer.PollingServerListUpdater类中,他这边有个start方法用来检查服务是否需要更新,每隔30s执行一次,并且回调
DynamicServerListLoadBalancer#updateListOfServers
方法,如下图所示:
- com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers
紧接着会回到DynamicServerListLoadBalancer并且调用updateListOfServers,这里有调用更新服务列表的时候有两个实现,最终会进入到ConfigurationBasedServerList:
- com.netflix.loadbalancer.ConfigurationBasedServerList#derive