前言
以下图是ribbon所有流程图:
可以结合这张图阅读源码。
一、ribbon的使用实例
1.1 服务端
@RestController
public class OrderService {
@Value("${server.port}")
private int port;
@GetMapping("/orders")
public String getAllOrder(){
System.out.println("port:"+port);
return "Return All Order";
}
}
启动两个服务端,一个端口是8080,一个是8082。
完成上述步骤后,服务端的两个地址:
http://localhost:8080/orders
http://localhost:8082/orders
1.2 客户端
1.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>
1.2.2 在application.properties中配置服务的提供者的地址列表
# 配置指定服务的提供者的地址列表
spring-cloud-order-service.ribbon.listOfServers=\
localhost:8080,localhost:8082
1.2.3 代码
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
return restTemplateBuilder.build();
}
/* @Autowired
LoadBalancerClient loadBalancerClient;*/
@GetMapping("/user/{id}")
public String findById(@PathVariable("id")int id){
//TODO
// 调用订单的服务获得订单信息
// HttpClient RestTemplate OkHttp JDK HttpUrlConnection
/* ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-order-service");
String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders");*/
return restTemplate.getForObject("http://spring-cloud-order-service/orders",String.class);
}
}
上述注释的这段代码应该更好理解:
ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-order-service");
String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders");
这个地方更下面的 restTemplate.getForObject("http://spring-cloud-order-service/orders",String.class); 做的事情是一样的。
上面三个步骤九可以用ribbon实现简单的负载了。
1.2.4 效果
1.访问客户端url:
2.服务端
3.客户端心跳
客户端会通过心跳,验证服务是否有效。
二、@Qualifier
为什么要讲@Qualifier这个注解呢,因为后面@LoadBalancer注解用到了它。
@Qualifier注解有什么用呢?
- 与@Autowired一起用,可以限定注入对象
- 可以放在自定义的注解上,修饰对象,等待注入的时候只会注入该注解修饰过的bean对象。@LoadBalancer就是这个原理
三、spi——RibbonAutoConfiguration.class
可以看到ribbon的jar包中的META-INF中的spring.factories文件中有如下配置:
可以看到文件中是一个key-value键值对:
- key是EnableAutoConfiguration类,是springboot的自动装载类
- value就是要被装载类的对象
首先经过spi,容器会自动装载RibbonAutoConfiguration.class类中@Bean注解生成的对象。下面我们来看一下RibbonAutoConfiguration类的关键代码截图:
RibbonAutoConfiguration类先方法一下,后面再分析,先看一下LoadBalancerAutoConfigration类。
四、spi——LoadBalancerAutoConfigration.class
如上,想看spring.factories:
点进LoadBalancerAutoConfigration类,它主要干了三件事:
- 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求的拦截, 以现客户端负均衡
- 创建一个RestTemplateCustonizer的Bean,用RestTemplate 增加LoadBalancerlnterceptor拦截器
- 维护了一个@LoadBalance的RestTemplate列表,并初始化,通过调用RestTemplateCustomlizer的实例来给客户端RestTemplate增加LoadBalancerInterceptor拦截器
以上是我的分析,后来我在网上也看到了讲解,在这里贴上,朋友们也可以直接看这里:
* Copyright 2013-2017 the original author or authors.
package org.springframework.cloud.client.loadbalancer;
...
@Configuration
@ConditionalOnClass(RestTemplate.class)// 当前环境需要有RestTemplate.class
@ConditionalOnBean(LoadBalancerClient.class)// 需要当前环境有LoadBalancerClient接口的实现类
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)// 初始化赋值LoadBalancerRetryProperties
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
// 1.@AutoWired也会自动装载集合类list,会将合适的RestTemplate添加到restTemplates中
// 而至于加载哪些RestTemplate,就是标注了@LoadBalanced的RestTemplate
// 上面我们看到@LoadBalanced有一个@Qualifier就是特殊标注的含义,所以普通的没有添加@LoadBalanced
// 则不会被添加到restTemplates中的
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
// 2.SmartInitializingSingleton接口的实现类会在项目初始化之后被调用其afterSingletonsInstantiated方法
public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
}
};
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
// 3.LoadBalancerRequestFactory被创建
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
// 4.将LoadBalancerClient接口的实现类和3方法中创建的LoadBalancerRequestFactory
// 注入到该方法中,同时成为LoadBalancerInterceptor的参数
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
// 5. 方法4中创建的LoadBalancerInterceptor会被作为方法参数注入进来
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
// 5.1 customize方法会被2方法中的afterSingletonsInstantiated()遍历调用
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
// 有关于RetryTemplate相关的bean在该例中不会被加载进来
@Configuration
@ConditionalOnClass(RetryTemplate.class)
static class RetryAutoConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,
lbRetryPolicyFactory, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
五、Ribbon源码
先来看一下RestTemplate#doExecute方法,RestTemplate所有执行请求的代码,最终都会走到这里。
我把重点的两个步骤圈出来了:
接下来我们来分析这两处代码:
5.1 createRequest方法——创建request。
在restTemplate.execute代码中,在执行请求之前,会首先获取ClientHttpRequest对象。ClientHttpRequest是通过getRequestFactory()方法获取的。
5.2 execute方法
我们打断点可以追踪execute方法执行链路:
AbstractClientHttpRequest#execute——>AbstractBufferingClientHttpRequest#executeInternal——>InterceptingClientHttpRequest#executeInternal——>InterceptingClientHttpRequest#execute——>LoadBalancerInterceptor#intercept
根据上述链路,我们来追踪代码:
AbstractClientHttpRequest#execute:
AbstractBufferingClientHttpRequest#executeInternal:
InterceptingClientHttpRequest#executeInternal:
打断点来看一下:
InterceptingClientHttpRequest#execute:
执行到LoadBalancerInterceptor.intercept方法中:
接下来我们分析LoadBalancerInterceptor.intercept方法。
5.3 LoadBalancerInterceptor.intercept方法
RestTemplate发起请求的源码就不带大家分析了,不是很难,默认采用的是SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection来实现http请求的调用,我们主要关心请求的过程中,LoadBalancerInterceptor是如何发挥作用的。
最终还是回到了LoadBalancerInterceptor.intercept()这个方法上来,它主要实现了对于请求的拦截:
- 获得一个服务名称
- 调用loadBalancer.execut
LoadBalancerInterceptor.intercept调用链路:
LoadBalanceInterceptor#intercept——>RibbonLoadBalancerClient#execute——>RibbonLoadBalancerClient#getServer——>ZoneAwareLoadBalance#chooseServer
LoadBalanceInterceptor#intercept:
RibbonLoadBalancerClient#execute:
就是这里传过来的:
RibbonLoadBalancerClient#getServer:
ZoneAwareLoadBalance#chooseServer:
5.4 小结
分析到这里,我们已经搞明白了Ribbon是如何运用负载均衡算法来从服务列表中获得一个目标服务进行访问的。但是还有一个疑惑,就是动态服务列表的更新和获取是在哪里实现呢?
六、服务列表的加载过程
- allServerList:所有服务列表
- upServerList:可用的服务列表
通过定时任务更新这两个属性,默认30s更新一次;
10s心跳一次,检查列表中服务的可用性。
在本实例中,我们将服务列表配置在application.properties文件中,意味着在某个时候会加载这个列表,保存在某个位置,那它是在什么时候加载的呢?
具体流程如下图所示:
6.1 serverListImpl.getUpdatedListOfServers();
这个方法是获取更新的服务列表,实际调用的是 ConfigurationBasedServerList 中的getUpdatedListOfServers 方法。
public List<Server> getUpdatedListOfServers() {
String listOfServers =
clientConfig.get(CommonClientConfigKey.ListOfServers);
return derive(listOfServers);
}
6.2 RibbonClientConfiguration
ConfigurationBasedServerList 这个类,是在 RibbonClientConfiguration 这个配置类中进行初
始化的:
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
if (this.propertiesFactory.isSet(ServerList.class, name)) {
return this.propertiesFactory.get(ServerList.class, config, name);
}
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
return serverList;
}
七、ping的测试
7.1 添加自定义PING的代码
public class MyPing implements IPing{
public boolean isAlive(Serverserver){
System.out.println("isAlive"+server.getHostPort());
return true;
}
}
7.2 修改配置(注意,以下配置只需要关心倒数第一个和第二个即可)
#
配置服务器列表
MyRibbonClient.ribbon.listOfServers=localhost:8080,localhost:8002
#配置负载均衡规则IRule的实现类
MyRibbonClient.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.W
eightedResponseTimeRule
#配置负载均衡实现类
MyRibbonClient.ribbon.NFLoadBalancerClassName=com.netflix.loadbalancer.ZoneA
wareLoadBalancer
#配置IPing的实现类
MyRibbonClient.ribbon.NFLoadBalancerPingClassName=org.lixue.ribbon.client.My
Ping
#配置Ping操作的间隔
MyRibbonClient.ribbon.NFLoadBalancerPingInterval=2
八、RibbonLoadBalancerClient.execute
再回到RibbonLoadBalancerClient.execute方法中。通过getServer获得一个Server对象之后,再把Server包装成一个RibbonServer对象,这个对象保存了Server的所有信息,同时还保存了服务名、是否需要https等。
在调用另外一个execute重载方法,在这个方法中最终会调用apply方法,这个方法会向一个具体的实例发送请求:
8.1 request#apply方法
request是LoadBalancerRequest接口,它里面提供了一个apply方法,但是从代码中我们发现这个方法并没有实现类,那么它是在哪里实现的呢?
继续又往前分析发现,这个request对象是从LoadBalancerInterceptor的intercept方法中传递过来的:
而request的传递,是通过 this.requestFactory.createRequest(request, body, execution) 创建而来,于是我们找到这个方法:
从代码中发现,它是一个用lambda表达式实现的匿名内部类。在该内部类中,创建了一个
ServiceRequestWrapper,这个ServiceRequestWrapper实际上就是HttpRequestWrapper的一个子
类,ServiceRequestWrapper重写了HttpRequestWrapper的getURI()方法,重写的URI实际上就是通过调用LoadBalancerClient接口的reconstructURI函数来重新构建一个URI进行访问
九、ServiceRequestWrapper
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance,
this.getRequest().getURI());
return uri;
}
9.1 RibbonLoadBalancer.reconstructURI
reconstructURI这个方法,实际上是重构URI,也就是把一个 http://服务名/转化为 http://地址/ 的
过程。
- 首先获得一个serviceId
- 根据serviceId获得一个RibbonLoadBalancerContext对象,这个是用来存储一些被负载均衡器使用的上下文内容。
- 调用reconstructURIWithServer方法来构建服务实例的URI
9.1.2 LoadBalancerContext.reconstructURIWithServer
搞明白了URI的转化过程,我们再回到createRequest方法,最终会调用execution.execute来创建一个ClientHttpRespose对象。这里实际上是调用ClientHttpRequestExcution接口的execute方法。
而ClientHttpRequestExcution是一个接口,只有一个实现类,InterceptingRequestExecution。
9.1.3 InterceptingRequestExecution.execute
在该方法中,通过request.getURI()所返回的地址就是一个具体的目标服务地址。后续的请求过程就不需要再分析了,无非就是通过这个uri发起远程通信: