Feign源碼解析5:loadbalancer

背景

經過前面幾篇的理解,我們大致梳理清楚了FeignClient的創建、Feign調用的大體流程,本篇會深入Feign調用中涉及的另一個重要組件:loadbalancer,瞭解loadbalancer在feign調用中的職責,再追溯其是如何創建的。

在講之前,我先提個重點,本文章的前期是引用了nacos依賴且開啓瞭如下選項,啓用了nacos的Loadbalancer:

spring.cloud.loadbalancer.nacos.enabled=true

nacos的Loadbalancer是支持了基於nacos實例中的元數據進行服務實例篩選,比如權重等元數據。

不開這個選項,則是用默認的Loadbalancer,不知道支不支持基於nacos實例中的元數據進行服務實例篩選(沒測試)。

我們這邊是打開了這個選項,所以本文就基於打開的情況來講。

feign調用流程

大體流程

接上一篇文章,feign調用的核心代碼如下:

image-20240114113200793

1處主要是封裝請求;

2處主要是依靠loadbalancer獲取最終要調用的實例。

但是在1和2之間,有一段代碼是,獲取LoadBalancerLifecycle類型的bean列表,大家看到什麼lifecycle之類的名字,大概能知道,這些類是一些listener類,一般包含了幾個生命週期相關的方法,比如這裏就是:

void onStart(Request<RC> request);

void onStartRequest(Request<RC> request, Response<T> lbResponse);

void onComplete(CompletionContext<RES, T, RC> completionContext);

這幾個方法分別就是在loadbalancer的不同階段進行調用。

比如,我舉個例子,我之前發現feign的日誌裏沒打印最終調用的實例的ip、端口,導致查日誌不方便,所以我就定義了一個自定義的LoadBalancerLifecycle類,將最終選擇的實例的ip端口打印出來。

image-20240114113841901

我們看下,這裏是如何獲取LoadBalancerLifecycle對象的?

loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class)

工廠用途

loadBalancerClientFactory這個字段,類型爲LoadBalancerClientFactory,其定義:

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>

再看其註釋:

A factory that creates client, load balancer and client configuration instances. It creates a Spring ApplicationContext per client name, and extracts the beans that it needs from there.

這裏就直說了,這是個工廠,它會給每個client創建一個spring容器。這裏的client是啥呢,其實是org.springframework.cloud.client.loadbalancer.LoadBalancerClient類型的對象,它是在spring-cloud-commons中定義的接口:

image-20240114115218070

工廠自身的創建

工廠本身是自動裝配的:

image-20240114121947011

看上圖,需要一個構造函數參數,這個就是一些配置:

image-20240114122040282

調用的構造函數邏輯如下:

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>

public static final String NAMESPACE = "loadbalancer";    
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";

public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {
    super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
    this.properties = properties;
}

這裏調用了父類構造函數,把幾個值存到父類中:

private final String propertySourceName;
private final String propertyName;
private Class<?> defaultConfigType;

public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
}

完成構造後,我們發現,還調用了:

clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));

這裏的configurations類型是:

private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

image-20240114122524436

這個字段本身是通過構造函數方式注入的,來源呢,就是spring 容器。

我們有必要探究下,這個LoadBalancerClientSpecification類型的bean,是怎麼進入spring 容器的?

其實,這個類也是代表了一份LoadbalancerClient的配置,之前feignClient也是一樣的:

public class LoadBalancerClientSpecification implements NamedContextFactory.Specification {

	private String name;

	private Class<?>[] configuration;
}

這種類型的bean,其實是通過LoadBalancerClient註解和LoadBalancerClients註解進入容器的,當你使用這兩個註解時,其實是支持配置一個class:

image-20240114123017911

image-20240114123039013

然後,它們兩註解都import了一個LoadBalancerClientConfigurationRegistrar類:

image-20240114122913148

這個會負責將對應的配置class,註冊到容器中:

image-20240114123306881

註冊時,name會有所區別,如果是LoadBalancerClients註解引入的,會加個default前綴。

image-20240114123447968

在默認情況下(引入了nacos-discovery、spring-cloud-loadbalancer的情況下),就會在代碼中如下三處有@LoadBalancerClients註解:

image-20240114124649366

image-20240114124730849

image-20240114124757216

所以,我們工廠創建時debug,可以看到如下場景:

image-20240114124936926

從工廠獲取LoadBalancerLifecycle

上面講完了工廠的創建,這裏回到工廠的使用。我們之前看到,會獲取LoadBalancerLifecycle這種bean:

loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),

但奇怪的是,獲取bean不應該先用loadBalancerClientFactory創建的給各個loadBalancerClient的spring容器;再從容器獲取bean嗎?

這裏是簡化了,直接讓工廠負責全部事務,我要bean的時候,只找工廠要,工廠內部自己再去創建spring容器那些。

所以我們看到,工廠是實現了接口:

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
		implements ReactiveLoadBalancer.Factory<ServiceInstance>    

這個接口就有如下方法,這是個泛型方法:

Allows accessing beans registered within client-specific LoadBalancer contexts.
    
<X> Map<String, X> getInstances(String name, Class<X> type);

下面就看看方法如何實現的:

image-20240114125611955

這裏就是分了兩步,先獲取容器,再從容器獲取bean。

創建容器

這個獲取容器是先從緩存map獲取,沒有則創建。

image-20240114125754859

我們這裏自然是沒有的,進入createContext:

image-20240114130159006

這裏首先是創建了一個spring上下文,裏面是有一個bean容器的,容器裏要放什麼bean呢,首先就是上圖中的configurations中那些LoadBalancerClient註解裏指定的配置類,再然後,就是LoadBalancerClients註解裏指定的那些默認的配置類,我們這裏有3處LoadBalancerClients註解,但是隻有nacos那一個,指定了配置類:

@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class LoadBalancerNacosAutoConfiguration {

所以,這裏會把NacosLoadBalancerClientConfiguration這個配置類註冊到容器。

接下來,是如下這行:

context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);

這裏的defaultConfigType是啥呢,其實就是創建工廠時,指定的LoadBalancerClientConfiguration:

image-20240114130737961

到這裏爲止,基本spring容器該手工放入的bean就這些了。但這個容器內到時候只會有這些bean嗎,不是的。

因爲我們這裏放進去的幾個bean,內部又定義了更多的bean。

nacosLoadBalancerClientConfiguration
loadBalancerClientConfiguration    

nacosLoadBalancerClientConfiguration

首先是自動裝配一個NacosLoadBalancer(在缺少這種ReactorLoadBalancer bean的情況下)

image-20240114131703086

再下來,會自動裝配ServiceInstanceListSupplier bean:

image-20240114131857085

loadBalancerClientConfiguration

這邊注意,也是在沒註冊這個bean的時候,自動裝配ReactorLoadBalancer,這個其實會和上面的nacos的產生競爭,最終到底是哪個上崗呢,只能看順序了:

image-20240114132145192

和nacos一樣,自動裝配ServiceInstanceListSupplier:

image-20240114132231126

競爭關係誰勝出

我們上面提到,nacos的配置類和spring-cloud-loadbalancer的配置類,是全面競爭的,最終的話,是誰勝出呢?

我們看看容器完成bean創建後的情況:

image-20240114133009841

可以發現,是nacos的配置贏了。

具體爲什麼贏,這個暫時不細說,基本就是bean的order那些事情。反正現在nacos贏了,看起來也沒啥問題,我們就繼續往後走,目前是完成了bean容器的創建。

獲取LoadBalancerLifecycle類型bean

我這個項目,並沒定義這種bean,所以實際是取不到的,注意的是,在LoadbalancerClient對應的容器取不到,還是會去父容器取的。

我們在父容器也沒定義,所以最終是取不到。

根據服務名獲取最終實例

loadBalancerClient

目前準備分析如下代碼:

image-20240114133356196

先看下這個字段來自於哪裏:

image-20240114141150266

image-20240114141248933

可以看出,來自於spring容器注入。

image-20240114141418748

所以,這裏可以看出,loadBalancerClient類型爲BlockingLoadBalancerClient。

loadBalancerClient.choose

進入該方法:

image-20240114141024073

image-20240114141628863

image-20240114141647797

最終就是從容器獲取,取到的就是nacos自動裝配的NacosLoadBalancer:

image-20240114141739653

loadBalancer.choose

nacos這裏的實現用的反應式編程,不怎麼了解這塊,反正最終是調用getInstanceResponse方法,且會把從nacos獲取到的服務列表傳遞進來:

image-20240114142341843

image-20240114142555615

可以看到,這裏傳入的就是實際的服務實例,還包含了nacos相關的元數據,如cluster、weight、是否臨時、是否健康等。

後續的邏輯就根據實例的各種屬性進行篩選,如meta.nacos.cluster、ipv4/ipv6、

image-20240114142817880

根據權重進行選擇:

image-20240114143419421

根據實例進行feign調用

image-20240114143627515

我們跟進去後,發現主要就是feignClient.execute進行調用,在前後則是調用生命週期的相關方法:

image-20240114143957771

我們看到,這個client就是默認的FeignClient,比較原始,直接就是用原生的HttpURLConnection;我們之前文章提到,也是可以使用httpclient、okhttp那些feign.Client的實現,只要引入對應依賴即可。

image-20240114144221192

另外,這個也是沒有連接池的,每次都是打開新連接;這裏也用了外部options參數中的超時時間。

image-20240114144501645

後面的響應處理就略過不講了。

總結

我們總算是把大體流程都講完了,下一篇講講我遇到的問題。

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