背景
經過前面幾篇的理解,我們大致梳理清楚了FeignClient的創建、Feign調用的大體流程,本篇會深入Feign調用中涉及的另一個重要組件:loadbalancer,瞭解loadbalancer在feign調用中的職責,再追溯其是如何創建的。
在講之前,我先提個重點,本文章的前期是引用了nacos依賴且開啓瞭如下選項,啓用了nacos的Loadbalancer:
spring.cloud.loadbalancer.nacos.enabled=true
nacos的Loadbalancer是支持了基於nacos實例中的元數據進行服務實例篩選,比如權重等元數據。
不開這個選項,則是用默認的Loadbalancer,不知道支不支持基於nacos實例中的元數據進行服務實例篩選(沒測試)。
我們這邊是打開了這個選項,所以本文就基於打開的情況來講。
feign調用流程
大體流程
接上一篇文章,feign調用的核心代碼如下:
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端口打印出來。
我們看下,這裏是如何獲取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中定義的接口:
工廠自身的創建
工廠本身是自動裝配的:
看上圖,需要一個構造函數參數,這個就是一些配置:
調用的構造函數邏輯如下:
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;
這個字段本身是通過構造函數方式注入的,來源呢,就是spring 容器。
我們有必要探究下,這個LoadBalancerClientSpecification類型的bean,是怎麼進入spring 容器的?
其實,這個類也是代表了一份LoadbalancerClient的配置,之前feignClient也是一樣的:
public class LoadBalancerClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
這種類型的bean,其實是通過LoadBalancerClient註解和LoadBalancerClients註解進入容器的,當你使用這兩個註解時,其實是支持配置一個class:
然後,它們兩註解都import了一個LoadBalancerClientConfigurationRegistrar類:
這個會負責將對應的配置class,註冊到容器中:
註冊時,name會有所區別,如果是LoadBalancerClients註解引入的,會加個default
前綴。
在默認情況下(引入了nacos-discovery、spring-cloud-loadbalancer的情況下),就會在代碼中如下三處有@LoadBalancerClients註解:
所以,我們工廠創建時debug,可以看到如下場景:
從工廠獲取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);
下面就看看方法如何實現的:
這裏就是分了兩步,先獲取容器,再從容器獲取bean。
創建容器
這個獲取容器是先從緩存map獲取,沒有則創建。
我們這裏自然是沒有的,進入createContext:
這裏首先是創建了一個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:
到這裏爲止,基本spring容器該手工放入的bean就這些了。但這個容器內到時候只會有這些bean嗎,不是的。
因爲我們這裏放進去的幾個bean,內部又定義了更多的bean。
nacosLoadBalancerClientConfiguration
loadBalancerClientConfiguration
nacosLoadBalancerClientConfiguration
首先是自動裝配一個NacosLoadBalancer(在缺少這種ReactorLoadBalancer bean的情況下)
再下來,會自動裝配ServiceInstanceListSupplier bean:
loadBalancerClientConfiguration
這邊注意,也是在沒註冊這個bean的時候,自動裝配ReactorLoadBalancer,這個其實會和上面的nacos的產生競爭,最終到底是哪個上崗呢,只能看順序了:
和nacos一樣,自動裝配ServiceInstanceListSupplier:
競爭關係誰勝出
我們上面提到,nacos的配置類和spring-cloud-loadbalancer的配置類,是全面競爭的,最終的話,是誰勝出呢?
我們看看容器完成bean創建後的情況:
可以發現,是nacos的配置贏了。
具體爲什麼贏,這個暫時不細說,基本就是bean的order那些事情。反正現在nacos贏了,看起來也沒啥問題,我們就繼續往後走,目前是完成了bean容器的創建。
獲取LoadBalancerLifecycle類型bean
我這個項目,並沒定義這種bean,所以實際是取不到的,注意的是,在LoadbalancerClient對應的容器取不到,還是會去父容器取的。
我們在父容器也沒定義,所以最終是取不到。
根據服務名獲取最終實例
loadBalancerClient
目前準備分析如下代碼:
先看下這個字段來自於哪裏:
可以看出,來自於spring容器注入。
所以,這裏可以看出,loadBalancerClient類型爲BlockingLoadBalancerClient。
loadBalancerClient.choose
進入該方法:
最終就是從容器獲取,取到的就是nacos自動裝配的NacosLoadBalancer:
loadBalancer.choose
nacos這裏的實現用的反應式編程,不怎麼了解這塊,反正最終是調用getInstanceResponse方法,且會把從nacos獲取到的服務列表傳遞進來:
可以看到,這裏傳入的就是實際的服務實例,還包含了nacos相關的元數據,如cluster、weight、是否臨時、是否健康等。
後續的邏輯就根據實例的各種屬性進行篩選,如meta.nacos.cluster、ipv4/ipv6、
根據權重進行選擇:
根據實例進行feign調用
我們跟進去後,發現主要就是feignClient.execute進行調用,在前後則是調用生命週期的相關方法:
我們看到,這個client就是默認的FeignClient,比較原始,直接就是用原生的HttpURLConnection;我們之前文章提到,也是可以使用httpclient、okhttp那些feign.Client的實現,只要引入對應依賴即可。
另外,這個也是沒有連接池的,每次都是打開新連接;這裏也用了外部options參數中的超時時間。
後面的響應處理就略過不講了。
總結
我們總算是把大體流程都講完了,下一篇講講我遇到的問題。