背景
在feign中,一般是通過eureka、nacos等獲取服務實例,但有時候調用一些服務時,人家給的是ip或域名,我們這時候還能用Feign這一套嗎?
可以的。
有兩種方式,一種是直接指定url:
這種是服務端自己會保證高可用、負載均衡那些。
但也可能對方給了多個url(一般不會這樣,但是在app場景下,爲了極致的高可用,可能會配置多個服務端地址),此時就需要咱們在客戶端配置多個url,並且進行負載均衡。
此時應該怎麼配置呢?前面的文章提到了,可以像下面這樣配置:
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
但是,這第二種方式下,如果你同時使用了nacos,且打開了spring.cloud.loadbalancer.nacos.enabled=true
這個選項,就會發現,調用報錯了。
原因分析
從上面的錯誤堆棧可以看到,在執行Double.parseDouble的時候拋了空指針異常,爲啥還涉及什麼浮點數呢?
我們定位到報錯的地方,原來是獲取服務實例的權重值的時候,報錯了:
很明顯,是因爲我們的服務實例裏面的metadata字段,沒有nacos.weight這個屬性,所以是null,自然就空指針了。
這裏的服務實例是ServiceInstance,這是個通用接口,定義在spring-cloud-commons中的,按理說,你作爲一種實現,是需要考慮到傳入的ServiceInstance不一定就有這個屬性,比如可能是Eureka管理的。但是上面報錯的地方又強制假設這個地方一定是metadata擁有nacos.weight。
這塊就是個兼容性bug,看了下最新版本,也還是未修復:
接下來,我們看下,那如果是從nacos獲取到的serviceInstance,是不是就沒有這個問題?爲啥配置靜態ip地址的時候,就有這個問題。
nacos中獲取到的serviceInstance
咱們先把前面的靜態ip配置去掉,改爲從nacos獲取。
從上圖看到,此時實例類型是com.alibaba.cloud.nacos.NacosServiceInstance
:
此時自然就不會報錯了。
靜態ip時獲取到的serviceInstance
在獲取服務實例時,入口是org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient#getInstances
,它內部聚合了兩個discoveryClient,第一個是simpleDiscoveryClient,這個就是從靜態ip獲取服務實例,可以看到其order是-1,所以它排在了第一位;第二個是nacosDiscoveryClient,由於它的order值是0,所以排序靠後。
從simpleDiscoveryClient中獲取到的serviceInstance的類型就是org.springframework.cloud.client.DefaultServiceInstance
,它內部自然是沒有配置nacos相關的metadata的,所以在前面的場景中才會報錯。
解決辦法一
既然nacos這個loadbalancer不兼容靜態ip這種org.springframework.cloud.client.DefaultServiceInstance,那我不使用nacos的loadbalancer不就可以了。
是的,只要你不打開spring.cloud.loadbalancer.nacos.enabled=true
這個選項,就不會用到nacos的這個loadbalancer。
我們搜了下這個選項:
這被弄成了一個條件註解。這個條件用於以下的自動裝配類:
在之前的文章裏,我們提到了,每個feign服務只要url沒指定,就默認是走負載均衡,就會有一個loadbalancerClient。
每個loadbalancerClient都是通過一個spring容器來的,每個服務都有一個自己的用於創建loadbalancer的spring容器(比如這裏的echo-service,就有一個自己的用於創建loadbalancer的spring容器)。這個容器裏面默認有啥內容呢?
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
這裏的NacosLoadBalancerClientConfiguration.class就會被作爲各個spring容器的默認配置類。
這裏就會自動配置一個NacosLoadBalancer,一旦有了這個bean,spring-cloud-loadbalancer裏的默認配置,就不會生效了:
最終獲取bean的時候,就拿到了nacos的這個NacosLoadBalancer類型的bean,進行負載均衡。
這個辦法的缺點:
這個選項是全局的,不能針對某一個服務來單獨開啓,這個選項一旦關了,那麼其他的走nacos的服務,也就沒法用nacosLoadBalancer了。
所以,我們想到了如下的方法。
解決辦法二
我們上面提到,這個nacosLoadBalancer被自動裝配進去的,那麼,破解自動裝配的辦法就是你自己定義一個這種類型的bean,它就不會再自動裝配了。
這樣的話,echo-service-provider的spring容器創建時,就會優先把這個配置class註冊到容器裏:
這種辦法的優勢是,可以在spring.cloud.loadbalancer.nacos.enabled=true
開啓的情況下,解決本文的問題。就是,nacos的依然可以用nacosLoadBalancer來負載均衡;靜態ip的服務,就可以用輪詢這種loadbalancer。
總結
這個feign寫得差不多了,後面寫點別的。如果後續需要補充這塊,再說。
參考
官網有類似bug: