在前面的文章,博主聊了Ribbon如何与SpringCloud、Eureka集成,Ribbon如何自定义负载均衡策略:
- 【云原生&微服务一】SpringCloud之Ribbon实现负载均衡详细案例(集成Eureka、Ribbon)
- 【云原生&微服务二】SpringCloud之Ribbon自定义负载均衡策略(含Ribbon核心API)
前面我们学会了怎么使用Ribbon,那么为什么给RestTemplate类上加上了@LoadBalanced注解就可以使用Ribbon的负载均衡?SpringCloud是如何集成Ribbon的?Ribbon如何作用到RestTemplate上的?如何获取到的ILoadBalancer?
本文就这几个问题展开讨论。
PS: 文章中涉及到的SpringBoot相关知识点,比如自动装配,移步博主的SpringBoot专栏:Spring Boot系列。
PS2:Ribbon依赖Spring Cloud版本信息如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.3.7.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR8version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
下面以请求http://localhost:9090/say/saint
为入口进行debug。
这里我们就来看看为什么采用@Bean方法将RestTemplate注入到Spring容器时,加上@LoadBalanced注解就可以实现负载均衡?
@LoadBalanced在org.springframework.cloud.client.loadbalancer
包下,属于spring-cloud-commons
项目;
嗯,然后呢?既然是SpringCloud项目,二话不说开找自动装配类XXXAutoConfiguration 或 配置类XXXConfiguration。
上面说了@LoadBalanced属于spring-cloud-commons项目
,找到其jar包下/META-INF/spring.factories文件(为啥找这个文件呢,见SpringBoot自动装配机制原理):
既然是负载均衡,我们从类的命名来推测,锁定AsyncLoadBalancerAutoConfiguration
类和LoadBalancerAutoConfiguration
类;这两个类选哪个呢,“小公鸡点到谁就是谁”?还是看命名,AsyncLoadBalancerAutoConfiguration
中有Async,是异步负载均衡请求的;我们看同步,同步好debug,进一步锁定到LoadBalancerAutoConfiguration
类。
进到类里,我们发现它组合了一个RestTemplate集合,即:我们创建的那个RestTemplate实例(被@LoadBalanced注解标注
)会放到这里来!
- 此处细节涉及到SpringBoot的源码,为了避免偏题,本文仅提供一种思路,不做详细解释;
在LoadBalancerAutoConfiguration类中会注入一个SmartInitializingSingleton实例;
SmartInitializingSingleton接口中只有一个afterSingletonsInstantiated()
方法;在SpringBoot启动过程中,当RestTemplate实例化完之后,会执行这个方法,做如下操作:
- 遍历每个RestTemplate实例,然后再遍历所有的
RestTemplateCustomizer
对每个RestTemplate
实例做定制化操作,即添加拦截器LoadBalancerInterceptor
操作。
还是在LoadBalancerAutoConfiguration
类中会通过@Bean方法注入RestTemplateCustomizer
;
RestTemplateCustomizer所做的定制化就是给RestTemplate添加一个LoadBalancerInterceptor
拦截器;LoadBalancerInterceptor
和RestTemplateCustomizer
都在LoadBalancerAutoConfiguration的静态内部类LoadBalancerInterceptorConfig中,在SpringBoot启动流程中我们知道,同一个类中@Bean方法的加载是从上至下的,所以肯定是LoadBalancerInterceptor先加载到Spring容器中。
此外,LoadBalancerAutoConfiguration类中还会自动装配一些Retry…相关的类,用于请求重试。
看到这,我们知道了Ribbon如何作用到RestTemplate上!但是好像还差一点东西,我要通过RestTemplate做一个操作时,入口在哪?
上面我们说到,针对每一个RestTemplate,都会给其添加一个LoadBalancerInterceptor
拦截器,所以我们对RestTemplate执行某个操作时,会被LoadBalancerInterceptor所拦截。
LoadBalancerInterceptor中组合了LoadBalancerClient
,通过LoadBalancerClient
做负载均衡;
然而想要将LoadBalancerInterceptor注入到Spring容器,需要先将LoadBalancerClient
注入到Spring容器。那么LoadBalancerClient是何时注入的?
去哪找?我去哪找?既然我们集成了netflix-ribbon
,找一下以netflix-ribbon命名的jar包;找到spring-cloud-netflix-ribbon
jar包;老规矩,SpringClout项目直接就开找自动装配类XxxAutoConfiguration,自动装配类找不到注入Bean,再找配置类XxxConfiguration。
找到RibbonAutoConfiguration
类,其中会注入LoadBalancerClient
:
注意@AutoConfigureAfter
注解和@AutoConfigureBefore
注解,其表示:RibbonAutoConfiguration类加载要发生在LoadBalancerAutoConfiguration
类加载之前、发生在EurekaClientAutoConfiguration
类加载之后。
到这里,LoadBalancerClient
实例比定在LoadBalancerAutoConfiguration
加载之前已经注入到了Spring容器,我们回到LoadBalancerAutoConfiguration
类,看其中注入LoadBalancerInterceptor
类到Spring容器的地方;
LoadBalancerInterceptor类实现ClientHttpRequestInterceptor
接口,其中只有一个核心方法:intercept()
用于拦截通过RestTemplate执行的请求。
在intercept()方法中,会将RestTemplate执行请求的方法转发给RibbonLoadBalancerClient#execute()方法去执行;并基于RequestFactory创建出来的一个request给到RibbonLoadBalancerClient。
这里我们也就知道了真正执行RestTemplate请求方法的入口是RibbonLoadBalancerClient#execute()
。
下面我们就继续来看RibbonLoadBalancerClient#execute()
里面都做了什么?
从LoadBalancerInterceptor#intercept()方法进入到RibbonLoadBalancerClient#execute()方法代码执行流程如下:
最终进入到RibbonLoadBalancerClient#execute()方法中或做三件事:
- 根据服务名从Ribbon自己的
Spring子上下文
中获取服务名对应的ApplicationContext,进而获取到ILoadBalancer;- 根据负载均衡器
ILoadBalancer
从Eureka Client获取到的List
中选出一个Server。- 拼装真正的请求URI,做HTTP请求调用。
本文我们重点看一下如何获取到ILoadBalancer?
进入到#RibbonLoadBalancerClient#getLoadBalancer(String serviceId)
方法;
protected ILoadBalancer getLoadBalancer(String serviceId) {
// 通过SpringClientFactory来获取对应的LoadBalancer
return this.clientFactory.getLoadBalancer(serviceId);
}
其将请求交给SpringClientFactory的getLoadBalancer(String)方法处理:
看SpringClientFactory的类图:
SpringClientFactory继承自NamedContextFactory,所以super.getInstance(name, type)
方法为NamedContextFactory#getInstance()方法:
SpringClientFactory不是spring包下的,而是spring cloud与ribbon整合代码的包(org.springframework.cloud.netflix.ribbon)下的;
- 其对spring进行了一定程度上的封装,从spring里面获取bean的入口,都变成了这个spring cloud ribbon自己的SpringClientFactory;
- 也就是说:对于每个服务名称,都会有一个独立的spring的ApplicationContext容器(体现在NamedContextFactory类的contexts属性中);
- ApplicationContext中包含了自己这个服务的独立的一堆的组件,比如说LoadBalancer;
- 如果要获取一个服务对应的LoadBalancer,其实就是在自己的那个ApplicationContext里获取LoadBalancer接口类型的实例化Bean;
默认可以通过父类NamedContextFactory的getLoadBalacner()方法获取到ILoadBalancer接口对应的实例ZoneAwareLoadBalancer
。即获取到的ILoadBalancer为ZoneAwareLoadBalancer
。
在spring-cloud-netflix-ribbon
jar包下找到RibbonClientConfiguration
类,RibbonClientConfiguration类中加载了的ILoadBalancer的实例bean --> ZoneAwareLoadBalancer:
默认的LoadBalancer是ZoneAwareLoadBalancer,ZoneAwareLoadBalancer类图如下:
ZoneAwareLoadBalancer继承自DynamicServerListLoadBalancer,DynamicServerListLoadBalancer继承自BaseLoadBalancer。
下一篇文章,我们继续分析:
- ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表?
- ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息?
- 如何根据负载均衡器
ILoadBalancer
从Eureka Client获取到的List
中选出一个Server?