前一篇文章提到了我将项目的SpringCloud版本由Dalston.RELEASE升级到了Finchley.SR4。因为我没有使用Eureka,而是使用的ribbon.listOfServers来指定服务器,然后进行服务调用的,所以这里在升级后就遇到了一个坑:就是先前Dalston.RELEASE版本的时候服务可以调用成功,现在升级后反而服务调不通了。
懊恼痛苦中。。。。。。。。。。。。
我通过查看消费端的日志,发现消费端根本就没有获取到服务器地址。日志如下:
2021-05-18 20:17:06.003 INFO 44936 --- [ main] c.netflix.loadbalancer.BaseLoadBalancer : Client: SPRINGCLOUD-PROVIDER instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SPRINGCLOUD-PROVIDER,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2021-05-18 20:17:06.012 INFO 44936 --- [ main] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2021-05-18 20:17:06.021 INFO 44936 --- [ main] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client SPRINGCLOUD-PROVIDER initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=SPRINGCLOUD-PROVIDER,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@496cedbb
发现问题
于是我就纳闷,是什么原因导致ribbon.listOfServers失效了呢?
带着这个问题,我在网上查了资料,同时也通过自己的分析,发现日志中的服务信息是在DynamicServerListLoadBalancer这个类中打印的,那么服务信息是怎么传递到这个类中的呢?
查看源码,原来是通过ribbonLoadBalancer()方法中的new ZoneAwareLoadBalancer()来传递的服务信息的:
因此,服务信息就是ribbonLoadBalancer()方法的serverList参数决定的。
由于我们引入了spring-cloud-starter-netflix-eureka-client依赖,所以在配置类【RibbonClientConfiguration】和配置类【EurekaRibbonClientConfiguration】中均定义了获取该参数实例的@Bean方法【ribbonServerList】,并且都被【@ConditionalOnMissingBean】注解标注。这两者在上下文环境即配置文件中未指定参数【NIWSServerListClassName】的值时将会返回不同的默认类型。
【RibbonClientConfiguration】返回的是从参数【listOfServers】中获取服务器列表的【ConfigurationBasedServerList】,而【EurekaRibbonClientConfiguration】返回的是从Eureka服务端获取服务器列表的【DomainExtractingServerList】。
那么这里引入了一个问题。程序会加载配置类【RibbonClientConfiguration】和配置类【EurekaRibbonClientConfiguration】两个中的哪一个@Bean方法【ribbonServerList】呢?
分析过程
这里我在分析源码后,梳理了一下流程,分享给大家:
1.程序在启动时,会执行RibbonClientConfigurationRegistrar类,这个类里面会去扫描@RibbonClients和@RibbonClient注解,然后获取到它们的注解配置信息,将注解属性name和configuration的值设置到一个RibbonClientSpecification类型的对象中,再将该对象注册为spring容器中。代码截图如下:

这里我根据断点调试后发现有3个和@RobbinClient或@RobbinClients注解有关的配置。
1).我自己项目中自定义的配置:
@RibbonClient(name = “SPRINGCLOUD-PROVIDER”,configuration = MyConfiguration.class)
2).RibbonAutoConfiguration类,该类上有@RibbonClients注解
3).RibbonEurekaAutoConfiguration类,该类上有注解:@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
2.在将多个RibbonClientSpecification类型的bean注册到spring容器后,在RibbonAutoConfiguration类中进行List注入,然后在以@Bean注解的springClientFactory()方法里面进行获取,然后设置给【SpringClientFactory】对象。

这里的【SpringClientFactory】是个每个客户端用来创建Spring上下文的。会给每个Ribbon Client创建一个独立的Spring应用上下文ApplicationContext,并在其中加载对应的配置及Ribbon核心接口的实现类。
3.接着会执行RibbonApplicationContextInitializer的onApplicationEvent()方法,这个方法会调用springClientFactory.getContext(clientName)方法,继而会调NamedContextFactory类的getContext(String name)方法,然后又会调createContext(String name)方法,


这里的configurations包含3个元素,如下:

虽然前面在【RibbonAutoConfiguration】类中用@Bean注解定义SpringClientFactory且在创建SpringClientFactory对象时会给NamedContextFactory类的defaultConfigType属性赋值为RibbonClientConfiguration.class,但是在createContext()方法中属性【configurations】是优先于【defaultConfigType】,因为是先通过configurations里的对象进行context.register()注册到容器的。又因为configurations中的【default.org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration】没有指定配置类,也就是它的configuration属性为空,所以这个配置被忽略了,于是就注册了【EurekaRibbonClientConfiguration】对象。再因为@ConditionalOnMissingBean注解的原因,所以就不会执行【RibbonClientConfiguration】类的ribbonServerList()方法,只会执行【EurekaRibbonClientConfiguration】的ribbonServerList()方法。但是在此之前,它会先执行RibbonClientConfiguration的ribbonClientConfig()方法去获取配置信息。

在EurekaRibbonClientConfiguration的ribbonServerList()方法中,首先会先查看PropertiesFactory 里是否已经设置了此组件对应的数据。如果是,那么返回预先定义好的;如果不是,那么实时生成。
这样在EurekaRibbonClientConfiguration的ribbonServerList()方法中就可以直接获取到IClientConfig对象了:


因此,这里创建的ServerList对象就不包含了我们配置的ribbon.listOfServers的服务配置信息了。
解决方案
那么要怎么解决这个问题呢?
1、最简单的就是移除依赖【spring-cloud-starter-netflix-eureka-client】。
2、在不移除该依赖的情况下,可以通过在配置文件中指定参数【NIWSServerListClassName】为【com.netflix.loadbalancer.ConfigurationBasedServerList】
3、若不想移除依赖,那么可以设置属性【ribbon.eureka.enabled】为false,即可阻止其对Spring上下文生效。
补充
那么ribbon.listOfServers是在哪里加载的呢?
是在loadProperties()里面加载的,如下截图:



在将SpringCloud从Dalston.RELEASE升级到Finchley.SR4后,发现ribbon.listOfServers配置失效,服务调用失败。分析源码发现,由于EurekaRibbonClientConfiguration的存在,导致配置的服务器列表未被使用。解决方案包括移除Eureka依赖、指定NIWSServerListClassName或禁用ribbon.eureka.enabled。

697

被折叠的 条评论
为什么被折叠?



