Spring Cloud Ribbon 的请求分发与原理
前言
以下图是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 {@AutowiredRestTemplate restTemplate;@Bean@LoadBalancedpublic RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){return restTemplateBuilder.build();}/* @AutowiredLoadBalancerClient 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就是这个原理
拓展:
@Qualifier_junlon2006的博客-CSDN博客_qualifier
https://www.jb51.net/article/217180.htm
三、spi——RibbonAutoConfiguration.class
可以看到ribbon的jar包中的META-INF中的spring.factories文件中有如下配置:
可以看到文件中是一个key-value键值对:
- key是EnableAutoConfiguration类,是springboot的自动装载类
- value就是要被装载类的对象
首先经过spi,容器会自动装载RibbonAutoConfiguration.class类中@Bean注解生成的对象。下面我们来看一下RibbonAutoConfiguration类的关键代码截图:
注解 @AutoConfigureBefore 和 @AutoConfigureAfter 的用途_yangchao1125的博客-CSDN博客_autoconfigurebefore
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() {@Overridepublic 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 {@Beanpublic RetryTemplate retryTemplate() {RetryTemplate template = new RetryTemplate();template.setThrowLastExceptionOnExhausted(true);return template;}@Bean@ConditionalOnMissingBeanpublic LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();}@Beanpublic RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,LoadBalancerRequestFactory requestFactory) {return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,lbRetryPolicyFactory, requestFactory);}@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {return new RestTemplateCustomizer() {@Overridepublic 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发起远程通信:
参考文章:Spring Cloud源码分析:Ribbon如何为RestTemplate提供负载均衡_tanwubo的博客-CSDN博客
Spring Cloud Ribbon 的请求分发与原理相关推荐
- resttemplate 请求重试_使用Spring Cloud Ribbon重试请求
使用Spring Cloud Ribbon重试请求 在微服务调用中,一些微服务圈可能调用失败,通过再次调用以达到系统稳定性效果,本文展示如何使用Ribbon和Spring Retry进行请求再次重试调 ...
- 为Spring Cloud Ribbon配置请求重试(Camden.SR2+)
当我们使用Spring Cloud Ribbon实现客户端负载均衡的时候,通常都会利用@LoadBalanced来让RestTemplate具备客户端负载功能,从而实现面向服务名的接口访问(原理可见& ...
- Spring Cloud架构的各个组件的原理分析
我们先认识一下SpringCloud的各个组件,然后知其所以然. 原理讲解前,先看一个最经典的业务场景,如开发一个电商网站,要实现支付订单的功能,流程如下: 创建一个订单之后,如果用户立刻支付了这个订 ...
- Spring Cloud Ribbon(服务消费者)
Spring Cloud Ribbon 是一个基于Http和TCP的客户端负载均衡工具,基于Netflix Ribbon实现的.它不像服务注册中心.配置中心.API网关那样独立部署,但是它几乎存在于每 ...
- 【夯实Spring Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(上)
本文属于[夯实Spring Cloud]系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货.系列目录如下: [夯实Spring Cloud]D ...
- 3 Spring Cloud Ribbon
Spring Cloud Ribbon Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具. Netflix Ribbon 是 Net ...
- Spring Cloud Ribbon 是什么?
本文内容如有错误.不足之处,欢迎技术爱好者们一同探讨,在本文下面讨论区留言,感谢. 文章目录 简述 作用 客户端负载均衡 负载均衡算法 原理和使用 核心类 原理图 使用 小结 参考资料 简述 Spri ...
- Spring Cloud Ribbon的使用详解
目录 一.概述 1.Ribbon是什么 2.Ribbon能干什么 3.Ribbon现状 4.未来替代方案 5.架构说明 二.RestTemplate 用法详解 三.Ribbon核心组件IRule 四. ...
- Spring Cloud Ribbon负载均衡策略详解
通过之前的文章可以知道, Ribbon负载均衡器选择服务实例的方式是通过"选择策略"实现的, Ribbon实现了很多种选择策略,UML静态类图如上图. IRule是负载均衡的策略接 ...
- 基于Spring cloud Ribbon和Eureka实现客户端负载均衡
前言 本案例将基于Spring cloud Ribbon和Eureka实现客户端负载均衡,其中Ribbon用于实现客户端负载均衡,Eureka主要是用于服务注册及发现: 传统的服务端负载均衡 常见的服 ...
最新文章
- WF4.0实战(一):文件审批流程
- Adaboost通俗易懂入门教程
- javascript基础学习一--面向对象
- JAVA-重写equalse规范、技巧
- php 解析xml 的四种方法(转)
- sql优化基数和耗费_基数估计在SQL Server优化过程中的位置
- ubuntu14.04 安装pip vitualenv flask
- Java多态形式_Java多态
- java中普通变量、静态变量、静态代码块初始化的顺序辨析
- C++ STL 整理
- plsql32位链接64位oracle,32位PLsql连接64位Oracle问题
- 什么是网络处理器?网络处理器有哪些应用场景?
- html水平分割线虚线代码,CSS分割线虚线代码
- 视频配音怎么制作?手把手教你配音视频制作
- 如何隐藏Android模拟器的虚拟按键
- 二次元动漫卡通风格手机APP应用下载页自适应源码
- PES包的PTS详解
- ROS kinetic 机器视觉
- Unknown tag
- 期货ctp基础知识(合约,开仓,平仓,做多,做空,保证金,手续费)
热门文章
- php 不返回 数据,php – file_get_contents没有返回任何数据
- Google Code Review 代码审查速度
- 易筋SpringBoot 2.1 | 第十六篇:SpringBoot通过JDBC访问数据库
- 32位电脑适合装W ndows10,32位再见?微软将停止支持32位Win10系统
- 2021-08-25Prompt
- 2021-08-24fine-tuning 模型
- #include《》和#include“”的区别
- php 老是报错没有定义,php中的错误处理与异常处理机制介绍
- 实验板FPGA型号在哪里看_【VE】一文看懂乙烯基树脂发展史!
- Android事件处理