“不积跬步,无以至千里。”
这个专题开始,来深度刨析一下作为springcloud老牌的客户端负载均衡组件,Ribbon。
在项目中需要使用Ribbon,只需要使用@LoadBalanced
去标注一个RestTemplate
的bean即可,后续就可以在Controller中注入一个RestTemplate,调用getForObject()
之类的方法了
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
不过说实话,Ribbon在生产上一般用的比较少,因为代码写起来比较冗余,一般都会使用更好更简洁的组件Feign
因为Feign底层也是依赖于ribbon的,所以先了解ribbon的底层源码实现还是很有必要的。
那么去了解Ribbon源码之前,需要知道ribbon有几大核心的组件
ILoadBalancer
:ribbon核心中的核心,负载均衡器,是ribbon实现客户端负载均衡的组件
IRule
:ribbon核心组件之一,负载均衡规则器,负责从众多ServerList服务列表中使用某一规则挑选一个Server,然后发送Http请求
IPing
:ribbon组件之一,负载定时ping每个服务器,判断其是否还存活,实际上这个用处不是很大,后面会说,ribbon跟eureka整合之后,是依赖eureka的本地服务表,哪个服务宕机了,eureka server那里会有一套evict机制,eureka client定时增量拉取实例变更到本地就行,也用不着ribbon来操心,当然ribbon跟eureka整合后面也会具体剖析
ILoadBalancer
负载均衡器,底层是基于IRule
,负载均衡算法,从一堆服务器列表中选择一个server出来
可以通过实现IRule接口的方式来自定义负载均衡规则
public class CustomRule implements IRule {
ILoadBalancerr balancer;
public CustomRule() {
}
public CustomRule(ILoadBalancer balancer) {
this.balancer = balancer;
}
public Server choose(Object key) {
List<Server> servers = balancer.getAllServers();
return servers.get(0);
}
}
不过,在生产环境中,一般用它提供的一些负载均衡算法就已经可以满足绝大多数的场景了
那么ribbon中都有哪些负载均衡规则呢?
RoundRobinRule
:轮询,也是系统默认的负载均衡规则,从一堆server list中,不断的轮询来选择server,尽量保证请求能够分摊到每个server上
WeightedResponseTimeRule
:带着权重的,每个服务器可以有权重,权重越高优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问
RandomRule
:随机找一个服务器访问
RetryRule
:可以重试,通过round robin找到的服务器如果请求失败,可以重新找一个服务器
大致上就可以认为有这么几种,当然还有一些不常见的,就不一一列举了,感兴趣的可以自行百度一下
ok,开撸
首先,要找到ribbon源码的入口
我们可以从这个所谓的负载均衡注解@LoadBalanced
入手,毕竟只有这一个跟ribbon相关的配置
这个注解所在的包org.springframework.cloud.client.loadbalancer
,同包下找到一个LoadBalancerAutoConfiguration
,就是研究ribbon的入口,一般跟springboot整合的技术,都会搞一个XXAutoConfiguration或者XXXConfiguration,这是固定的套路和打法
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
... ...
}
customizer.customize(restTemplate)
关键的一行代码,首先restTemplate
就是我们定义在spring里面的RestTemplate bean,之前我们通过new的方式往spring容器里面放了一个,就会在这里取出来,然后调用一个RestTemplateCustomizer
的customize()
方法对这个bean进行一个“定制”
如果你在bean容器里没有定义RestTemplate,那么这整个自动配置类是不会执行的,@ConditionalOnClass(RestTemplate.class)
,这里人家已经使用了Conditional的派生注解做了限定
那么这个custom方法做了什么呢
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
主要是添加了一个拦截器loadBalancerInterceptor
,
啥意思,就是说,以后使用restTemplate调用它的方法,例如getForObject,postForEntity....
都会走这个拦截器
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
我们可以在这里推测一下这个拦截器会做的事情,动态代理!
因为我们使用ribbon来进行微服务调用,一般都会以这种格式来写
试想一下,如果使用原生的RestTemplate,它怎么能识别“springboot-project-2”这个东西?它只能识别ip和port然后去发送http请求
所以在这里这个拦截器,一定是做了一个关键的操作,把服务名称转成ip地址,而且一定是使用了内部的Rule组件,基于一个负载均衡算法,从多个服务实例中,选择一个来调用,最终发起http请求,然后获得响应
当然,这一切,都是我们基于最优解的一个合理猜想,可以先来看看这个所谓的LoadBalancerInterceptor
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
别的就不看了,就看看这个intercept方法
拿到服务名 ,originalUri.getHost()
调用了一个loadBalancer
组件的execute方法,把serviceName传进去,拿到结果
private LoadBalancerClient loadBalancer
接下来的方向应该就是好好看看这个LoadBalancerClient
的execute方法了。