前言
当我们使用 RestTemplate 调用服务时,会经过以下几个过程:
- RestTemplate 发送的请求被 LoadBalancerInterceptor 拦截,LoadBalancerInterceptor 调用 LoadBalancerClient.execute() 方法处理请求。LoadBalancerClient 默认使用的实现类是 RibbonLoadBalancerClient
 - 在 RibbonLoadBalancerClient.execute() 方法中,用 serviceId(被调方的服务名)获取一个 ILoadBalancer 对象,再调用 ILoadBalancer.chooseServer() 方法
 - 在 ILoadBalancer.chooseServer() 方法中,调用 IRule.choose() 方法,从被调服务的多个节点中选出一个节点
 - 选出服务节点后,把原本是服务名的请求 URI 换成这个节点的IP+端口,发送请求,得到响应
 
下面先讲第一个重点: ILoadBalancer 和 IRule 是怎么挑选被调服务的某个节点的?
PS:文中的源码版本是 SpringCloud Finchley.RELEASE, SpringBoot 2.0.9.RELEASE
挑选服务节点
这部分的逻辑有一丢丢复杂,我只选有关联的讲,能省则省,注意跟紧不要掉队咯~
ILoadBalancer
ILoadBalancer 的实现类的继承关系(从父类到子类):ILoadBalancer -> AbstractLoadBalancer -> BaseLoadBalancer -> DynamicServerListLoadBalancer -> ZoneAwareLoadBalancer
ILoadBalancer定义了挑选服务节点的基本方法:chooseServer。AbstractLoadBalancer只是给 ILoadBalancer 接口扩展了几个方法。
BaseLoadBalancer定义了成员变量IRule,IRule 的默认值为RoundRobinRule,就是轮询算法。它还实现了 chooseServer 方法,在方法中,直接调用 IRule.choose() 方法挑选服务节点。
DynamicServerListLoadBalancer未重写父类 BaseLoadBalancer 的 chooseServer 方法,但它实现了服务实例清单在运行期的动态更新能力(这个后面讲)。
ZoneAwareLoadBalancer重写了 chooseServer 方法,它比父类方法多了一个判断:被调服务的节点存在于一个 Zone 内还是分布在多个 Zone?
如果是分布在多个 Zone,ZoneAwareLoadBalancer 首先使用ZoneAvoidanceRule计算每个 Zone 的可用性,再从可用的 Zone 中随机选一个。选出一个 Zone 后,再用 IRule 从这个 Zone 内的节点列表内挑选一个节点。
如果就只存在于一个 Zone 内,那就不需要选择 Zone 了,直接用 IRule 从这个 Zone 内的节点列表内挑选一个节点。
因此,无论是哪种情况,最后都是调用成员 IRule 的 choose 方法挑选服务节点。
在配置类RibbonClientConfiguration中,配置了 ILoadBalancer 实现类是ZoneAwareLoadBalancer,IRule 实现类是ZoneAvoidanceRule。所以在默认配置下,最后负责挑选服务节点的方法是ZoneAvoidanceRule.choose()。
不了解什么是 Zone?这里简单介绍下:
Zone 和 Region 是两个区域的概念。一个 Region 下可以包含多个 Zone,一个 Zone 只对应一个 Region。这两个属性的意义在于可以指定客户端的某个节点位于某个 Zone 内,这个客户端调用其他服务时,优先在自己的 Zone 内选被调服务的节点,如果没有再去别的 Zone 选服务节点,减少了远程调用的次数。
对于这两个属性的概念,我也参照其他博客的理解:Region 代表地区,如北京、新加坡,Zone 代表机房,如北京下有两个机房:bj-zone1 和 bj-zone2。
我们没有使用到这两个属性时,每个节点的注册中心地址配置都是eureka.client.serviceUrl.defalutZone = xxx。在这个配置下,所有节点所在的 Region =”us-east-1”,Zone = “defalutZone”,这两个默认值在EurekaClientConfigBean中指定。
IRule
因为默认使用的是 ZoneAvoidanceRule,所以这里只介绍它和它的父类们 O(∩_∩)O
继承关系(从父类到子类):IRule -> AbstractLoadBalancerRule -> ClientConfigEnabledRoundRobinRule -> PredicateBasedRule -> ZoneAvoidanceRule
IRule接口和AbstractLoadBalancerRule抽象类定义了挑选服务节点的基本方法:choose,以及成员ILoadBalancer lb,ILoadBalancer 在 IRule 中的作用就是“获取服务节点列表”。【结合上面介绍的 BaseLoadBalancer,可以看出 ILoadBalancer 和 IRule 互相依赖
ClientConfigEnabledRoundRobinRule实现了 choose 方法:调用 RoundRobinRule.choose() 挑选服务节点。这个类的作用是为其子类的 choose 方法提供一个备选方案。
PredicateBasedRule是个抽象类,定义了获取AbstractServerPredicate对象的方法,交由子类实现。它的 choose 方法就是调用 AbstractServerPredicate 对象的 chooseRoundRobinAfterFiltering 方法挑选服务节点并返回。
下面先介绍一下 AbstractServerPredicate.chooseRoundRobinAfterFiltering() 方法,源码如下:
1  | public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {  | 
如上,chooseRoundRobinAfterFiltering 方法把形参 ListgetEligibleServers方法进行过滤,得到过滤后的节点列表。如果返回的列表为空,则用 Optional.absent() 表示服务不存在,反之则以线性轮询 的方式从过滤后的节点列表中获取一个实例并返回。
getEligibleServers 方法:
1  | public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {  | 
在这个方法中,进入 else 分支,遍历 servers,对每个 server 执行 apply 方法,apply 方法就是服务过滤的逻辑,apply 方法返回true,就将此 server 加入要返回的列表,false就不加入。
什么情况下不进入 else 分支?
很少。因为从RibbonLoadBalancerClient.execute() 方法开始一直传进来的 loadBalancerKey=”default”,所以都会进入 else 分支。
综上,AbstractServerPredicate.chooseRoundRobinAfterFiltering() 是一个模板方法,定义了处理被调服务节点列表的模板:先过滤列表,再轮询选择。其中用于过滤服务节点列表的 apply 方法由 AbstractServerPredicate 的子类实现。
AbstractServerPredicate 有一个重要的子类:CompositePredicate。
CompositePredicate 成员变量包括一个主过滤条件AbstractServerPredicate delegate和一组次过滤条件List<AbstractServerPredicate> fallbacks。它重写了 getEligibleServers 方法:
1  | 
  | 
如上,它的 getEligibleServers 方法先调用了父类 AbstractServerPredicate 的同名方法,父类的这个方法又会调用子类(此处是 CompositePredicate)的 apply 方法。CompositePredicate 的 apply 方法就是使用主过滤条件 ———— delegate.apply()。
从父类方法返回 List
说回到 PredicateBasedRule,它的子类ZoneAvoidanceRule使用的 AbstractServerPredicate 对象就是 CompositePredicate 类,其中主过滤条件是ZoneAvoidancePredicate,次过滤条件只有AvailabilityPredicate。
ZoneAvoidanceRule未重写父类的 choose 方法,所以它的 choose 方法的调用链为:
ZoneAvoidanceRule.choose() -> AbstractServerPredicate.chooseRoundRobinAfterFiltering() -> CompositePredicate.getEligibleServers()
因此,ZoneAvoidanceRule.choose() 逻辑为“先过滤列表,再轮询选择”。过滤节点列表时,先用 ZoneAvoidancePredicate 过滤一遍,如果过滤后的列表个数小于1,才会使用到次过滤条件 AvailabilityPredicate。
ZoneAvoidancePredicate 和 AvailabilityPredicate 的过滤逻辑,简单地说就是:
ZoneAvoidancePredicate 计算了列表内所有节点所在的 Zone 的可用性,只留下了在可用 Zone 内的节点;
AvailabilityPredicate 排除了断路器打开(说明服务可能故障)或并发请求数过多的节点。
因为很少使用次过滤条件,我们可以简单地说:ZoneAvoidanceRule 先筛选出在可用 Zone 内的被调服务节点列表,再用“线性轮询”的方法从中选出一个。
至此,默认配置下的挑选服务的逻辑讲完啦~
总结
在默认配置下,负责挑选服务节点的是 ZoneAwareLoadBalancer.chooseServer() 方法。
ZoneAwareLoadBalancer.chooseServer() 方法先判断:被调服务的节点存在于一个 Zone 内还是分布在多个 Zone?有多个 Zone 就先计算每个 Zone 的可用性,从可用的 Zone 中随机选一个 Zone。如果只有一个 Zone 就不需要选择了。
确定好 Zone 后,再调用 ZoneAvoidanceRule.choose() 挑选服务节点,此时待挑选的服务列表内的节点都是存在于这个确定好的 Zone 内。
ZoneAvoidanceRule 的 choose 方法就是调用其成员 CompositePredicate 的 chooseRoundRobinAfterFiltering 方法。在这个方法中,先过滤被调服务的节点列表,再用“线性轮询”的方法从过滤后的列表选出一个节点。
过滤列表时,先使用的是 ZoneAvoidancePredicate.apply() 方法,它计算了列表内所有节点所在的 Zone 的可用性,只留下了在可用 Zone 内的节点。
如果 ZoneAvoidancePredicate 过滤后的列表不为空,就用“线性轮询”的方法从这个列表里选出一个节点,结束。【考虑到此时待挑选的列表内的节点都是存在于一个 Zone 内,除非这个 Zone 变得不可用了,不然经过 ZoneAvoidancePredicate 过滤后的列表都是不为空的
如果 ZoneAvoidancePredicate 过滤后的列表为空,再使用 AvailabilityPredicate.apply() 方法过滤,它排除了断路器打开(说明服务可能故障)或并发请求数过多的节点。最后也是用“线性轮询”的方法从过滤后的列表里选出一个节点,结束。
扩展内容:如果现在需要你自己实现“灰度部署”或“蓝绿部署”,是不是就有思路了?
哎,就是先自定义一个 AbstractServerPredicate 的子类,在这个子类的 apply 方法里编写过滤服务的代码,比如在“灰度发布”阶段,灰度请求只能进入灰度节点,那么在 apply 方法里就要排除掉正常节点,只留下灰度节点。【至于如何识别正常服务还是灰度服务,可以借助eureka.instance.metadata-map属性,在服务的元数据里体现
然后还需要自定义一个 PredicateBasedRule 的子类,它使用的 AbstractServerPredicate 对象就是刚刚自定义的 AbstractServerPredicate 子类。最后要记得配置 IRule Bean 是自己创建的那个 PredicateBasedRule 子类哦~
Github上有一个实现蓝绿发布的工程:https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter 。作者就是在自定义的 AbstractServerPredicate 子类中,过滤出和调用方 flag 一样的节点,比如调用方是蓝节点,就只过滤出被调方的蓝节点。
获取服务列表
上面我们提到,ILoadBalancer 也是 IRule 的成员,它在 IRule 中的作用就是“获取服务节点列表”。那么问题来了,ILoadBalancer 是怎么获取服务节点列表的?
ILoadBalancer 接口定义了两个方法:
(1) List<Server> getReachableServers()————获取正常服务节点
(2) List<Server> getAllServers()————获取所有服务节点,包括正常的和停止的节点
IRule.choose() 方法就是先调用 ILoadBalancer 的这两个方法的其中一个,如 PredicateBasedRule 是调用 ILoadBalancer.getAllServers(),获取被调服务的节点列表,再用特定算法选出一个节点。
BaseLoadBalancer 实现了这两个方法,如下:
1  | // 所有服务,包括正常和停止  | 
DynamicServerListLoadBalancer 未重写这两个方法,它实现的是对这两个服务列表,allServerList 和 upServerList 的动态更新。
为了实现服务列表的动态更新,DynamicServerListLoadBalancer 定义了一个成员:ServerList<T> serverListImpl,默认初始化 serverListImpl = DomainExtractingServerList。DomainExtractingServerList 对象里又包含一个成员:ServerList<DiscoveryEnabledServer> list,默认初始化 list = DiscoveryEnabledNIWSServerList。
这个“套娃”关系展示如下:
ServerList接口有两个方法,如下:
1  | public interface ServerList<T extends Server> {  | 
DynamicServerListLoadBalancer 就是依赖 ServerList 的 getUpdatedListOfServers 方法获取最新的服务列表,调用链为:
DynamicServerListLoadBalancer.updateListOfServers() -> DomainExtractingServerList.getUpdatedListOfServers() -> DiscoveryEnabledNIWSServerList.getUpdatedListOfServers() -> DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery()
这个调用链最后一环DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery()调用了DiscoveryClient.getInstancesByVipAddress()方法,这个方法根据 serviceId(被调服务的名字),优先从同个 Region 中找服务节点,找到了就返回服务节点列表,同个 Region 下没有再去别的 Region 中找【DiscoveryClient 的服务列表的来源就是注册中心
DiscoveryEnabledNIWSServerList 得到 DiscoveryClient 返回的节点列表后,从该列表选出状态为 UP 的节点,把每个服务节点包装成DiscoveryEnabledServer对象。
DomainExtractingServerList 得到 DiscoveryEnabledNIWSServerList 返回的节点列表后,再把每个节点包装成DomainExtractingServer对象返回给 DynamicServerListLoadBalancer,其中每个节点的 Zone 属性来源于节点的元数据配置,配置项为eureka.instance.metadata-map.zone。
上面的调用链中第一个方法DynamicServerListLoadBalancer.updateListOfServers(),就是负责更新 allServerList 和 upServerList 的方法。这个方法的源码如下:
1  | public void updateListOfServers() {  | 
如上,从 serverListImpl.getUpdatedListOfServers() 得到节点列表后,接着使用成员filter对这个列表进行过滤, filter 默认初始化为ZonePreferenceServerListFilter。
ZonePreferenceServerListFilter 过滤列表时,先调用父类方法。在父类方法中,判断是否启用“区域感知”功能(shouldEnableZoneAffinity),判断的依据是当前区域(Zone)是否健康(有几个指标来评估),如果当前 Zone 健康,就启用“区域感知”功能,过滤出和调用方 Zone 一致的节点并返回。如果当前 Zone 不健康,就不启用“区域感知”,返回的列表是没过滤的、分布在多个 Zone 的被调方节点列表。
从父类方法返回后,ZonePreferenceServerListFilter 接着判断父类是不是已经做过过滤了(过滤后的列表和过滤前是否一样)。如果已经被过滤了,就直接返回父类的结果。如果没有被过滤,则子类再从列表中过滤出和调用方 Zone 一致的节点,返回。
总之,经过 ZonePreferenceServerListFilter 的过滤,被调方节点列表中只留下了和调用方在同一个 Zone 内的节点。这就实现了“优先调用同个 Zone 内的节点”。但,前提是被调方在这个 Zone 内有节点,否则结果依然是没过滤的、分布在其他多个 Zone 的被调方节点列表。
filter 过滤完后,把过滤后的、最新的节点列表传入updateAllServerList方法,这个方法内接着调用DynamicServerListLoadBalancer.setServersList()方法。
DynamicServerListLoadBalancer.setServersList() 方法源码:
1  | public void setServersList(List lsrv) {  | 
方法形参List lsrv就是过滤后的、最新的节点列表,这个方法做了3件事:
- 调用父类 BaseLoadBalancer 的 setServersList 方法,这个方法将 allServerList 和 upServerList 都更新为 lsrv
 - 整理过滤后的节点列表,根据节点所在的 Zone 进行分类,分类完后是一个 Map,key 为 Zone 的名字,value 为这个 Zone 下的节点列表
 - 把分类后的 Map 传给子类方法 ZoneAwareLoadBalancer.setServerListForZones()
 
ZoneAwareLoadBalancer.setServerListForZones()方法源码:
1  | 
  | 
这个方法也做了3件事:
- 调用父类 DynamicServerListLoadBalancer 的 setServerListForZones 方法,记录一下参数 Map 里拥有的 Zone
 - 对每个 Zone 都初始化一个
BaseLoadBalancer对象,然后把这个 Zone 内的节点列表赋值给这个 BaseLoadBalancer 的 allServerList 和 upServerList - 检查成员 balancers 内是不是有过期的 Zone 信息,即参数 Map 里已经没有这个 Zone 了但 balancers 里还有,把过期的 Zone 对应的 BaseLoadBalancer 的服务列表置为空
 
对每个 Zone 初始化 BaseLoadBalancer 对象时,初始化的这个 BaseLoadBalancer 对象的成员 IRule = ZoneAvoidanceRule(因为是从 ZoneAwareLoadBalancer 的 IRule 克隆过来的),属性 name = 服务名 + “_” + zone名。Zone 和 BaseLoadBalancer 的对应关系保存在成员ConcurrentHashMap<String, BaseLoadBalancer> balancers内。
服务列表的更新过程说完了,现在我们简单提一下何时更新服务列表?
DynamicServerListLoadBalancer 还有这样两个成员:
1  | protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {  | 
成员serverListUpdater是 PollingServerListUpdater 类,在 RibbonClientConfiguration 中配置的。这是个定时任务,它周期性(默认30秒)执行ServerListUpdater.UpdateAction.doUpdate()方法。如上,这个 doUpdate 方法就是执行 DynamicServerListLoadBalancer.updateListOfServers() 方法。
总结
BaseLoadBalancer 定义了保存服务节点列表的成员:allServerList 和 upServerList。
DynamicServerListLoadBalancer 借助成员 ServerListUpdater,周期性(默认30秒)调用 updateListOfServers 方法更新服务节点列表。
在 updateListOfServers 方法中,DynamicServerListLoadBalancer 再借助成员 serverListImpl = DomainExtractingServerList 从注册中心获取被调方节点列表,优先获取同个 Region 内的节点列表,每个节点都是 UP 状态,且带有 Zone 信息。
接着,DynamicServerListLoadBalancer 再调用成员 filter = ZonePreferenceServerListFilter 过滤出和调用方 Zone 一致的被调方节点。若被调方在这个 Zone 内没有节点,则 filter 返回的还是没过滤的、分布在其他多个 Zone 的节点列表。
最后,过滤完的节点列表更新到 allServerList 和 upServerList。ZoneAwareLoadBalancer 还会根据节点所在的 Zone 对节点进行分类。每个 Zone 对应一个 BaseLoadBalancer 对象(若没有会自动初始化),这个 BaseLoadBalancer 对象保存的节点列表就是仅限于这个 Zone 内的节点,成员 IRule 也是 ZoneAvoidanceRule。
关于“优先调用同一个 Zone 内的节点”:
前面说到,调用方会优先调用在同一个 Zone 内的被调方节点,原因就是待挑选的服务节点列表经过 ZonePreferenceServerListFilter 过滤后只剩下了在同一个 Zone 内的节点。调用 ZoneAwareLoadBalancer.chooseServer() 时,因为列表中的节点只存在于一个 Zone,所以使用的 IRule 就是 ZoneAwareLoadBalancer 的成员,待选的服务列表也是 ZoneAwareLoadBalancer 维护的 allServerList,内容是这同一个 Zone 内的被调方节点。
如果被调方在这个 Zone 内没有节点,只在另一个 Zone 内有节点,则 ZonePreferenceServerListFilter 不会做任何过滤,ZoneAwareLoadBalancer 得到的还是在另一个 Zone 内的节点列表。调用 ZoneAwareLoadBalancer.chooseServer() 时,因为列表中的节点只存在于一个 Zone,所以挑选的逻辑还是和上面一样。
如果被调方在这个 Zone 内没有节点,在其他多个 Zone 内有节点,则 ZonePreferenceServerListFilter 不会做任何过滤,ZoneAwareLoadBalancer 把每个 Zone 下的节点列表整理出来,保存在每个 Zone 对应的 BaseLoadBalancer 对象中。调用 ZoneAwareLoadBalancer.chooseServer() 时,因为列表中的节点分布在多个 Zone,就先选出一个 Zone,取这个 Zone 对应的 BaseLoadBalancer 对象,再调用这个 BaseLoadBalancer 的 chooseServer 方法选出服务节点。自然而然,方法内使用的 IRule 就是 BaseLoadBalancer 的成员,待选的服务列表也是这个 BaseLoadBalancer 的 allServerList,内容是仅限于这个 Zone 内的节点。
不管是用谁的 IRule,这个 IRule 都是 ZoneAvoidanceRule,待选的服务列表都是在一个 Zone 内的节点。只要这个 Zone 在这调用过程中保持正常不故障,ZoneAvoidanceRule 就是用“线性轮询”的方式,从这个 Zone 的节点列表内选一个节点,结束。
组件的自动配置
负载均衡中的重点已经说完了,这里是一些旁支末节,可以不用理会~我写下来是因为这部分源码中有一些实现方式蛮新鲜的,所以特地记录一下。
在“前言”部分已经列出了负载均衡的过程,其中前两点是:
- RestTemplate 发送的请求被 LoadBalancerInterceptor 拦截,LoadBalancerInterceptor 调用 LoadBalancerClient.execute() 方法处理请求。LoadBalancerClient 默认使用的实现类是 RibbonLoadBalancerClient
 - 在 RibbonLoadBalancerClient.execute() 方法中,用 serviceId(被调方的服务名)获取一个 ILoadBalancer 对象,再调用 ILoadBalancer.chooseServer() 方法
 
除了 ILoadBalancer 和 IRule,整个过程用到的组件还有拦截请求的 LoadBalancerInterceptor,具体处理请求的 RibbonLoadBalancerClient,这部分内容就介绍下这几个组件是怎么配置的。
LoadBalancerInterceptor的配置
在配置类LoadBalancerAutoConfiguration中,首先扫描被@LoadBalanced修饰的 RestTemplate Bean,然后为每个 RestTemplate Bean 添加拦截器LoadBalancerInterceptor。添加了拦截器后,RestTemplate 发出的每个请求都会被拦截。
在这个配置类之前还会加载另一个配置类RibbonAutoConfiguration,注入LoadBalancerClientBean(实现类是RibbonLoadBalancerClient),这个 Bean 成为了 LoadBalancerInterceptor 的成员,最终处理请求的方法就是LoadBalancerClient.execute()方法。
LoadBalancerInterceptor 配置源码如下:
1  | 
  | 
这部分是我第一次看到@Autowired的这个用法:在 @Autowired 注解上加另一个注解,就会扫描出所有加了这个注解的同类的 Bean 放到 List 内。
ILoadBalancer的配置
请求被拦截后,由 RibbonLoadBalancerClient.execute() 方法处理请求,首先根据 serviceId(服务名)取 ILoadBalancer 对象,负责这件事的是 RibbonLoadBalancerClient 的成员SpringClientFactory。
SpringClientFactory 根据 serviceId 取 IloadBalancer,实际上是根据 serviceId 取 IoC 容器,再从 IoC 容器中取 IloadBalancer Bean。serviceId 和 IoC 容器的关系保存在成员Map<String, AnnotationConfigApplicationContext> contexts中。如果 serviceId 还没有对应的 IoC 容器,就先创建一个以便下次取用。因此,ILoadBalancer 和 IRule 的初始化不是在系统启动时,而是在第一次调用某个服务的时候。
一个分布式应用一般要调用多个服务,按照这个设计,调用方要为每个被调服务创建一个 IoC 容器,这个容器中的 ILoadBalancer、IRule 等都是调用这个服务专用的。这种做法比起用同一个 ILoadBalancer Bean 调所有服务,可以更好的维护每个服务的节点列表,使它们之间互不干扰。
SpringClientFactory 中创建容器的方法源码(方法参数 name 就是 serviceId):
1  | protected AnnotationConfigApplicationContext createContext(String name) {  | 
可以看到,创建的容器注册了4个东西:
- Map<String, C> configurations 中 key=serviceId 的 value 的 getConfiguration() 返回值
 - Map<String, C> configurations 中 以”default.”开头的 key 对应的 value 的 getConfiguration() 返回值
 - PropertyPlaceholderAutoConfiguration.class
 - defaultConfigType
 
其中,第三个不用关注,第四个 defaultConfigType =RibbonClientConfiguration.class,这个属性是在构造方法SpringClientFactory()中设置。
接下来研究下 SpringClientFactory 的成员Map<String, C> configurations的内容是什么。
它的来源在RibbonAutoConfiguration配置类,对,就是注入了 RibbonLoadBalancerClient 的那个类。
源码如下:
1  | public class RibbonAutoConfiguration {  | 
所以,Map<String, C> configurations 的来源是所有RibbonClientSpecificationBean。那么,RibbonClientSpecification Bean 又在哪注册呢?它又包含了什么内容?
在配置类RibbonEurekaAutoConfiguration中,通过@RibbonClients注解,import 另一个类RibbonClientConfigurationRegistrar。
RibbonEurekaAutoConfiguration 源码:
1  | // ...  | 
RibbonClients 源码:
1  | // ...  | 
RibbonClientConfigurationRegistrar 继承自ImportBeanDefinitionRegistrar,它是和@Import注解配合的一个类:使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar接口的实现类,则会调用接口方法registerBeanDefinitions,在这个方法里可以注入 Bean。
在 RibbonClientConfigurationRegistrar 类中,调用了 registerBeanDefinitions 方法后,如果 RibbonClients 的 defaultConfiguration 属性有值,就会进入registerClientConfiguration方法(方法源码如下),在这个方法内创建了 RibbonClientSpecification Bean并注入。
1  | private void registerClientConfiguration(BeanDefinitionRegistry registry,  | 
如上,因为这个 @RibbonClients 注解是标在RibbonEurekaAutoConfiguration类上,结合 RibbonEurekaAutoConfiguration 类的源码,这个 RibbonClientSpecification 对象的 name = “default.org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration”,configuration = EurekaRibbonClientConfiguration.class。
然后,这个 RibbonClientSpecification 对象被扫描到这里:
1  | (required = false)  | 
再然后,SpringClientFactory 把这个 ListMap<String, C> configurations,key = RibbonClientSpecification 对象的 name 属性,value = RibbonClientSpecification 对象,如下所示:
1  | public void setConfigurations(List<C> configurations) {  | 
所以,SpringClientFactory 创建容器时注册的4个东西分别是:
- Map<String, C> configurations 中 key=serviceId 的 value 的 getConfiguration() 返回值 – 没有这个 key
 - Map<String, C> configurations 中 以”default.”开头的 key 对应的 value 的 getConfiguration() 返回值 – RibbonClientSpecification.getConfiguration() 返回值 = EurekaRibbonClientConfiguration.class
 - PropertyPlaceholderAutoConfiguration.class
 - defaultConfigType – RibbonClientConfiguration.class
 
综上,serviceId 对应的 IoC 容器中加载了配置类EurekaRibbonClientConfiguration和RibbonClientConfiguration。其中,RibbonClientConfiguration 注入了 ILoadBalancer = ZoneAwareLoadBalancer、IRule = ZoneAvoidanceRule。
补充介绍一下 ZoneAwareLoadBalancer 的初始化过程。
配置 ZoneAwareLoadBalancer 的源码(在 RibbonClientConfiguration 类):
1  | 
  | 
ZoneAwareLoadBalancer 初始化时,先调用父类 DynamicServerListLoadBalancer 初始化, DynamicServerListLoadBalancer 再调用父类 BaseLoadBalancer 初始化。当前 BaseLoadBalancer 的 name = serviceId。
DynamicServerListLoadBalancer 从父类 BaseLoadBalancer 初始化返回后,再调用 restOfInit 方法,这个方法又会调用到 updateListOfServers 方法。对,这个就是 DynamicServerListLoadBalancer 更新服务列表的方法。所以,ZoneAwareLoadBalancer 初始化时,也把它的服务列表初始化好了,它的成员ConcurrentHashMap<String, BaseLoadBalancer> balancers———— Zone 和 BaseLoadBalancer 的对应关系也初始化好了。
在没有配置多个 Zone 的一般项目中,如果你在这个初始化过程里调试 BaseLoadBalancer 的构造方法,你就会发现 BaseLoadBalancer 的构造方法被调用了两次,第一次调用是因为 ZoneAwareLoadBalancer 初始化要调用父类构造方法,name 属性为 serviceId;第二次调用是因为 ZoneAwareLoadBalancer 要创建 defaultZone 对应的 BaseLoadBalancer 对象,name 属性为 serviceId + “_defaultzone”。
配置类的加载顺序
系统启动时,根据spring.factories文件加载了配置类RibbonAutoConfiguration,其中注入了 RibbonLoadBalancerClient 。
RibbonAutoConfiguration 注入了 RibbonLoadBalancerClient Bean 后,加载LoadBalancerAutoConfiguration,这个类为 RestTemplate 设置了 LoadBalancerInterceptor。
RibbonAutoConfiguration 加载完后,加载RibbonEurekaAutoConfiguration,通过@RibbonClients注解,import RibbonClientConfigurationRegistrar 类,这个类注入了 RibbonClientSpecification ,这个对象中包含配置类EurekaRibbonClientConfiguration的 class 信息。
系统中第一次使用 RestTemplate 调用服务时,为这个服务初始化一个 IoC 容器,IoC 容器加载的配置类包括EurekaRibbonClientConfiguration和RibbonClientConfiguration。其中,RibbonClientConfiguration 注入了 ILoadBalancer = ZoneAwareLoadBalancer、IRule = ZoneAvoidanceRule。