Feign源码解析7:nacos loadbalancer不支持静态ip的负载均衡
背景
在feign中,一般是通過eureka、nacos等獲取服務(wù)實(shí)例,但有時(shí)候調(diào)用一些服務(wù)時(shí),人家給的是ip或域名,我們這時(shí)候還能用Feign這一套嗎?
可以的。
有兩種方式,一種是直接指定url:
這種是服務(wù)端自己會保證高可用、負(fù)載均衡那些。
但也可能對方給了多個(gè)url(一般不會這樣,但是在app場景下,為了極致的高可用,可能會配置多個(gè)服務(wù)端地址),此時(shí)就需要咱們在客戶端配置多個(gè)url,并且進(jìn)行負(fù)載均衡。
此時(shí)應(yīng)該怎么配置呢?前面的文章提到了,可以像下面這樣配置:
spring:
application:
discovery:
client:
simple:
instances:
echo-service-provider:
- uri: http://1.1.1.1:8082
metadata:
my: instance1
- uri: http://2.2.2.2:8082
metadata:
my: instance2
但是,這第二種方式下,如果你同時(shí)使用了nacos,且打開了spring.cloud.loadbalancer.nacos.enabled=true這個(gè)選項(xiàng),就會發(fā)現(xiàn),調(diào)用報(bào)錯(cuò)了。
原因分析
從上面的錯(cuò)誤堆棧可以看到,在執(zhí)行Double.parseDouble的時(shí)候拋了空指針異常,為啥還涉及什么浮點(diǎn)數(shù)呢?
我們定位到報(bào)錯(cuò)的地方,原來是獲取服務(wù)實(shí)例的權(quán)重值的時(shí)候,報(bào)錯(cuò)了:
很明顯,是因?yàn)槲覀兊姆?wù)實(shí)例里面的metadata字段,沒有nacos.weight這個(gè)屬性,所以是null,自然就空指針了。
這里的服務(wù)實(shí)例是ServiceInstance,這是個(gè)通用接口,定義在spring-cloud-commons中的,按理說,你作為一種實(shí)現(xiàn),是需要考慮到傳入的ServiceInstance不一定就有這個(gè)屬性,比如可能是Eureka管理的。但是上面報(bào)錯(cuò)的地方又強(qiáng)制假設(shè)這個(gè)地方一定是metadata擁有nacos.weight。
這塊就是個(gè)兼容性bug,看了下最新版本,也還是未修復(fù):
https://github.com/alibaba/spring-cloud-alibaba/blob/2022.x/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-discovery/src/main/java/com/alibaba/cloud/nacos/balancer/NacosBalancer.java#L58
接下來,我們看下,那如果是從nacos獲取到的serviceInstance,是不是就沒有這個(gè)問題?為啥配置靜態(tài)ip地址的時(shí)候,就有這個(gè)問題。
nacos中獲取到的serviceInstance
咱們先把前面的靜態(tài)ip配置去掉,改為從nacos獲取。
從上圖看到,此時(shí)實(shí)例類型是com.alibaba.cloud.nacos.NacosServiceInstance:
此時(shí)自然就不會報(bào)錯(cuò)了。
靜態(tài)ip時(shí)獲取到的serviceInstance
在獲取服務(wù)實(shí)例時(shí),入口是org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient#getInstances,它內(nèi)部聚合了兩個(gè)discoveryClient,第一個(gè)是simpleDiscoveryClient,這個(gè)就是從靜態(tài)ip獲取服務(wù)實(shí)例,可以看到其order是-1,所以它排在了第一位;第二個(gè)是nacosDiscoveryClient,由于它的order值是0,所以排序靠后。
從simpleDiscoveryClient中獲取到的serviceInstance的類型就是org.springframework.cloud.client.DefaultServiceInstance,它內(nèi)部自然是沒有配置nacos相關(guān)的metadata的,所以在前面的場景中才會報(bào)錯(cuò)。
解決辦法一
既然nacos這個(gè)loadbalancer不兼容靜態(tài)ip這種org.springframework.cloud.client.DefaultServiceInstance,那我不使用nacos的loadbalancer不就可以了。
是的,只要你不打開spring.cloud.loadbalancer.nacos.enabled=true這個(gè)選項(xiàng),就不會用到nacos的這個(gè)loadbalancer。
我們搜了下這個(gè)選項(xiàng):
這被弄成了一個(gè)條件注解。這個(gè)條件用于以下的自動裝配類:
在之前的文章里,我們提到了,每個(gè)feign服務(wù)只要url沒指定,就默認(rèn)是走負(fù)載均衡,就會有一個(gè)loadbalancerClient。
每個(gè)loadbalancerClient都是通過一個(gè)spring容器來的,每個(gè)服務(wù)都有一個(gè)自己的用于創(chuàng)建loadbalancer的spring容器(比如這里的echo-service,就有一個(gè)自己的用于創(chuàng)建loadbalancer的spring容器)。這個(gè)容器里面默認(rèn)有啥內(nèi)容呢?
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
這里的NacosLoadBalancerClientConfiguration.class就會被作為各個(gè)spring容器的默認(rèn)配置類。
這里就會自動配置一個(gè)NacosLoadBalancer,一旦有了這個(gè)bean,spring-cloud-loadbalancer里的默認(rèn)配置,就不會生效了:
最終獲取bean的時(shí)候,就拿到了nacos的這個(gè)NacosLoadBalancer類型的bean,進(jìn)行負(fù)載均衡。
這個(gè)辦法的缺點(diǎn):
這個(gè)選項(xiàng)是全局的,不能針對某一個(gè)服務(wù)來單獨(dú)開啟,這個(gè)選項(xiàng)一旦關(guān)了,那么其他的走nacos的服務(wù),也就沒法用nacosLoadBalancer了。
所以,我們想到了如下的方法。
解決辦法二
我們上面提到,這個(gè)nacosLoadBalancer被自動裝配進(jìn)去的,那么,破解自動裝配的辦法就是你自己定義一個(gè)這種類型的bean,它就不會再自動裝配了。
這樣的話,echo-service-provider的spring容器創(chuàng)建時(shí),就會優(yōu)先把這個(gè)配置class注冊到容器里:
這種辦法的優(yōu)勢是,可以在spring.cloud.loadbalancer.nacos.enabled=true開啟的情況下,解決本文的問題。就是,nacos的依然可以用nacosLoadBalancer來負(fù)載均衡;靜態(tài)ip的服務(wù),就可以用輪詢這種loadbalancer。
總結(jié)
這個(gè)feign寫得差不多了,后面寫點(diǎn)別的。如果后續(xù)需要補(bǔ)充這塊,再說。
參考
官網(wǎng)有類似bug:
https://github.com/alibaba/spring-cloud-alibaba/issues/3346
總結(jié)
以上是生活随笔為你收集整理的Feign源码解析7:nacos loadbalancer不支持静态ip的负载均衡的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【南大静态代码分析】作业 1:活跃变量分
- 下一篇: 【架构师视角系列】Apollo配置中心之