Feign源碼解析7:nacos loadbalancer不支持靜態ip的負載均衡

背景

在feign中,一般是通過eureka、nacos等獲取服務實例,但有時候調用一些服務時,人家給的是ip或域名,我們這時候還能用Feign這一套嗎?

可以的。

有兩種方式,一種是直接指定url:

image-20240121151018163

這種是服務端自己會保證高可用、負載均衡那些。

但也可能對方給了多個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這個選項,就會發現,調用報錯了。

image-20240121151628850

原因分析

從上面的錯誤堆棧可以看到,在執行Double.parseDouble的時候拋了空指針異常,爲啥還涉及什麼浮點數呢?

我們定位到報錯的地方,原來是獲取服務實例的權重值的時候,報錯了:

image-20240121151853048

很明顯,是因爲我們的服務實例裏面的metadata字段,沒有nacos.weight這個屬性,所以是null,自然就空指針了。

這裏的服務實例是ServiceInstance,這是個通用接口,定義在spring-cloud-commons中的,按理說,你作爲一種實現,是需要考慮到傳入的ServiceInstance不一定就有這個屬性,比如可能是Eureka管理的。但是上面報錯的地方又強制假設這個地方一定是metadata擁有nacos.weight。

這塊就是個兼容性bug,看了下最新版本,也還是未修復:

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

image-20240121152632222

接下來,我們看下,那如果是從nacos獲取到的serviceInstance,是不是就沒有這個問題?爲啥配置靜態ip地址的時候,就有這個問題。

nacos中獲取到的serviceInstance

咱們先把前面的靜態ip配置去掉,改爲從nacos獲取。

image-20240121153456819

從上圖看到,此時實例類型是com.alibaba.cloud.nacos.NacosServiceInstance:

image-20240121153542584

此時自然就不會報錯了。

靜態ip時獲取到的serviceInstance

image-20240121153910030

在獲取服務實例時,入口是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的,所以在前面的場景中才會報錯。

image-20240121153833435

解決辦法一

既然nacos這個loadbalancer不兼容靜態ip這種org.springframework.cloud.client.DefaultServiceInstance,那我不使用nacos的loadbalancer不就可以了。

是的,只要你不打開spring.cloud.loadbalancer.nacos.enabled=true這個選項,就不會用到nacos的這個loadbalancer。

我們搜了下這個選項:

image-20240121154722753

這被弄成了一個條件註解。這個條件用於以下的自動裝配類:

image-20240121154806179

image-20240121154854657

在之前的文章裏,我們提到了,每個feign服務只要url沒指定,就默認是走負載均衡,就會有一個loadbalancerClient。

每個loadbalancerClient都是通過一個spring容器來的,每個服務都有一個自己的用於創建loadbalancer的spring容器(比如這裏的echo-service,就有一個自己的用於創建loadbalancer的spring容器)。這個容器裏面默認有啥內容呢?

@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)

這裏的NacosLoadBalancerClientConfiguration.class就會被作爲各個spring容器的默認配置類。

image-20240121155450960

這裏就會自動配置一個NacosLoadBalancer,一旦有了這個bean,spring-cloud-loadbalancer裏的默認配置,就不會生效了:

image-20240121155754346

最終獲取bean的時候,就拿到了nacos的這個NacosLoadBalancer類型的bean,進行負載均衡。

image-20240121160127329

這個辦法的缺點:

這個選項是全局的,不能針對某一個服務來單獨開啓,這個選項一旦關了,那麼其他的走nacos的服務,也就沒法用nacosLoadBalancer了。

所以,我們想到了如下的方法。

解決辦法二

我們上面提到,這個nacosLoadBalancer被自動裝配進去的,那麼,破解自動裝配的辦法就是你自己定義一個這種類型的bean,它就不會再自動裝配了。

image-20240121162841370

image-20240121162914911

這樣的話,echo-service-provider的spring容器創建時,就會優先把這個配置class註冊到容器裏:

image-20240121163142851

這種辦法的優勢是,可以在spring.cloud.loadbalancer.nacos.enabled=true開啓的情況下,解決本文的問題。就是,nacos的依然可以用nacosLoadBalancer來負載均衡;靜態ip的服務,就可以用輪詢這種loadbalancer。

總結

這個feign寫得差不多了,後面寫點別的。如果後續需要補充這塊,再說。

參考

官網有類似bug:

https://github.com/alibaba/spring-cloud-alibaba/issues/3346

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章