前言
这篇博文着重讲源码,对于这种比较复杂的源码,我喜欢截图分析,因为图可以作标注,比直接贴源码要方便和醒目。本篇博文会有大量的图片,显得文章会很长,其实文字是不多的。建议读者自己配合源码阅读。注意版本保持一致,我的版本下面会给出。
我的很多分析在图片上会有标注,读者请关注图片上的被红框部分以及标注部分。
一、Ribbon的作用
Ribbon的作用是负载均衡。使用Ribbon实现负载均衡时,基本用法是注入一个RestTemplate,并使用@LoadBalanced注解标注RestTemplate,从而使RestTemplate具备负载均衡的能力。
当Spring容器启动时,使用@LoadBalanced注解修饰的RestTemplate会被添加拦截器,拦截器中使用了LoadBalancerClient处理请求,从而达到负载均衡的目的。那么LoadBalancerClient内部是如何做到的呢?下面我们通过源码分析的方式来剖析Ribbon负载均衡的工作原理。
二、demo
2.1 pom.xml配置增加ribbon依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
2.2 application.properties配置{spring.application.name}.ribbon.listOfServers
在没有配置注册中心的情况下,使用{spring.application.name}.ribbon.listOfServers配置写死服务端节点。
# 配置指定服务的提供者的地址列表
spring-cloud-order-service.ribbon.listOfServers=\
localhost:8080,localhost:8082
2.3 应用
三、源码分析一:RibbonLoadBalanceClient装配过程
网上的图:
接下来我们会根据上图来分析ribbon原理。
先分析这一部分:
首先我们要知道,RibbonLoadBalanceClient是ribbon负载的重要类。至于RibbonLoadBalanceClient在ribbon负载是怎样一个流程,我们后面再说,这一部分先看它是如何添加到spring中的。
3.1RibbonAutoConfiguration配置类
看呀,spring-cloud-netflix-ribbon-2.2.3.RELEASE.jar下面的spring.factories,有如下配置:
如上图我说的,SPI!自动装载!
在RibbonAutoConfiguration配置类上,我们可以看到下面这部分
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
表示RibbonAutoConfiguration.class会在LoadBalancerAutoConfiguration.class之前装载。
3.2 LoadBalancerAutoConfiguration配置类
LoadBalancerAutoConfiguration配置类的生成情况:
接下来我们看LoadBalancerAutoConfiguration.class类:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = 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);
}
}
});
}
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@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);
};
}
}
/**
* Auto configuration for retry mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* Auto configuration for retry intercepting mechanism.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
在该自动化配置类中,主要做了下面三件事:
- 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
- 创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。
- 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。
到这里,对比图1分析下来,流程是不是还挺清晰的呀。
一个疑问:
那么我打个断点调试一下:
可以看到我@Bean生成的LoadBalanceInterceptor生成的bean对象是@6409,在下面
restTemplateCustomizer方法中,只要看看LoadBalanceInterceptor对象是不是@6409就行,然而它不是:
它是@6413,所以这俩不是一个 LoadBalanceInterceptor对象,至于 restTemplateCustomizer()方法闯进来的@6413LoadBalanceInterceptor对象是从哪里生成以及注入的,后面再说,值得一提的是这两个对象的属性引用是一致的:
扩展:
3.3@Qualifier和@LoadBalanced
四、从restTemplate#getForObject来分析Ribbon源码
首先我们知道ribbon是通过拦截器来实现负载的。那么发起一个请求,是怎么如何到拦截器的呢?带着这个问题,我们来看一下源码。
看一下RestTemplate类结构图:
看下调用流程:
restTemplate#getForObject——>restTemplate#execute——>restTemplate#doExecute
接下来我们来看restTemplate#doExecute源码:
4.1 createRequest方法——得到一个ClientHttpRequest对象
我们着重看#createRequest方法,和ClientHttpRequest#execute方法。
createRequest方法是在父类中实现的,父类——HttpAccessor.class:
继续看HttpAccessor#createRequest方法:
点进InterceptingHttpAccessor#getRequestFactory查看:
其中,getRequestFactory方法代码如下,其中getInterceptors是获得当前客户端请求的所有拦截
器,需要注意的是,这里的拦截器,就包含LoadBalancerInterceptor。
来解答上图②问题,这里我们前面分析过了,来看:
来看红圈③,创建出来的是InterceptorClientHttpRequestFactory工厂类,点createRequest方法:
想看它的类继承图:
它的父类是AbstactBufferingClientHttpRequest,来看父类AbstactBufferingClientHttpRequest代码:
所以得到的是InterceptingClientHttpRequest!
4.2 分析request.execute()方法,执行请求逻辑
搞明白上面的流程了,那么接下来就是到下一阶段了,查看request.execute():
类结构图:
进入AbstractBufferingClientHttpRequest#executeInternal方法:
进入到InterceptingClientHttpRequest# executeInternal:
为了证实我上面的说法,我们打断点可以看到,确实是含有拦截器的:
既然分析到这里了,我们不妨就进到LoadBalancerInterceptor拦截器里去瞅瞅:
接着分析:
上图②:深入解析
先看一下ZoneAwareLoadBalancer:
代码追踪到这里,其他的我们先放一放。接下来看服务在LoadBancer中是怎么缓存进来的。
4.3 LoadBancer如何缓存服务
接下来的逻辑就在下图红色虚线框中了:
想看BaseLoadBalancer类:
我们可以看到BaseLoadBalancer类有allServerList和upServerList属性,看名字就知道,allServerList-所有服务列表,upServerList-目前在线的服务列表。
这里是在哪里设置,从哪里获取到的值?
接下来的流程就是下图的深蓝色实线框内的步骤了:
请看下面:
继续看父类DynamicServerListLoadBalancer类的有参构造函数:
进入红框①代码:
默认30s更新一次
进入红框②代码:
到这里,这一块就分析结束了。
4.4 ribbon如何初始化几个重要的对象
把视线再移到这一块:
看一下RibbonClientConfiguration是怎么初始化下面这几个对象的:
PollingServerListUpdater:
PollingServerListUpdater通过这个@Bean方法注入进去的。
这个方法的参数是config,config的bean是怎么创建的呢,请看下面:
这里先分析到这。
4.5 DynamicServerListLoadBalancer#updateListOfServers方法
下面回到updateListOfServers方法,来讲它的代码:
点进去瞅一下是怎么更新的:
4.6 心跳
再初始化BaseLoadBalancer构造函数时,有下面这一句代码:
这里我就把PingUrl.isAlive()代码贴一下,以后我们写心跳可以参考:
public boolean isAlive(Server server) {
String urlStr = "";
if (this.isSecure) {
urlStr = "https://";
} else {
urlStr = "http://";
}
urlStr = urlStr + server.getId();
urlStr = urlStr + this.getPingAppendString();
boolean isAlive = false;
HttpClient httpClient = new DefaultHttpClient();
HttpUriRequest getRequest = new HttpGet(urlStr);
String content = null;
try {
HttpResponse response = httpClient.execute(getRequest);
content = EntityUtils.toString(response.getEntity());
isAlive = response.getStatusLine().getStatusCode() == 200;
if (this.getExpectedContent() != null) {
LOGGER.debug("content:" + content);
if (content == null) {
isAlive = false;
} else if (content.equals(this.getExpectedContent())) {
isAlive = true;
} else {
isAlive = false;
}
}
} catch (IOException var11) {
var11.printStackTrace();
} finally {
getRequest.abort();
}
return isAlive;
}
好了,本篇博文先到这里结束。下一篇我们还将继续分析ribbon,敬请关注。