Feign源码解析5:loadbalancer
背景
經(jīng)過前面幾篇的理解,我們大致梳理清楚了FeignClient的創(chuàng)建、Feign調(diào)用的大體流程,本篇會深入Feign調(diào)用中涉及的另一個重要組件:loadbalancer,了解loadbalancer在feign調(diào)用中的職責(zé),再追溯其是如何創(chuàng)建的。
在講之前,我先提個重點,本文章的前期是引用了nacos依賴且開啟了如下選項,啟用了nacos的Loadbalancer:
spring.cloud.loadbalancer.nacos.enabled=true
nacos的Loadbalancer是支持了基于nacos實例中的元數(shù)據(jù)進(jìn)行服務(wù)實例篩選,比如權(quán)重等元數(shù)據(jù)。
不開這個選項,則是用默認(rèn)的Loadbalancer,不知道支不支持基于nacos實例中的元數(shù)據(jù)進(jìn)行服務(wù)實例篩選(沒測試)。
我們這邊是打開了這個選項,所以本文就基于打開的情況來講。
feign調(diào)用流程
大體流程
接上一篇文章,feign調(diào)用的核心代碼如下:
1處主要是封裝請求;
2處主要是依靠loadbalancer獲取最終要調(diào)用的實例。
但是在1和2之間,有一段代碼是,獲取LoadBalancerLifecycle類型的bean列表,大家看到什么lifecycle之類的名字,大概能知道,這些類是一些listener類,一般包含了幾個生命周期相關(guān)的方法,比如這里就是:
void onStart(Request<RC> request);
void onStartRequest(Request<RC> request, Response<T> lbResponse);
void onComplete(CompletionContext<RES, T, RC> completionContext);
這幾個方法分別就是在loadbalancer的不同階段進(jìn)行調(diào)用。
比如,我舉個例子,我之前發(fā)現(xiàn)feign的日志里沒打印最終調(diào)用的實例的ip、端口,導(dǎo)致查日志不方便,所以我就定義了一個自定義的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創(chuàng)建一個spring容器。這里的client是啥呢,其實是org.springframework.cloud.client.loadbalancer.LoadBalancerClient類型的對象,它是在spring-cloud-commons中定義的接口:
工廠自身的創(chuàng)建
工廠本身是自動裝配的:
看上圖,需要一個構(gòu)造函數(shù)參數(shù),這個就是一些配置:
調(diào)用的構(gòu)造函數(shù)邏輯如下:
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;
}
這里調(diào)用了父類構(gòu)造函數(shù),把幾個值存到父類中:
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;
}
完成構(gòu)造后,我們發(fā)現(xiàn),還調(diào)用了:
clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
這里的configurations類型是:
private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;
這個字段本身是通過構(gòu)造函數(shù)方式注入的,來源呢,就是spring 容器。
我們有必要探究下,這個LoadBalancerClientSpecification類型的bean,是怎么進(jìn)入spring 容器的?
其實,這個類也是代表了一份LoadbalancerClient的配置,之前feignClient也是一樣的:
public class LoadBalancerClientSpecification implements NamedContextFactory.Specification {
	private String name;
	private Class<?>[] configuration;
}
這種類型的bean,其實是通過LoadBalancerClient注解和LoadBalancerClients注解進(jìn)入容器的,當(dāng)你使用這兩個注解時,其實是支持配置一個class:
然后,它們兩注解都import了一個LoadBalancerClientConfigurationRegistrar類:
這個會負(fù)責(zé)將對應(yīng)的配置class,注冊到容器中:
注冊時,name會有所區(qū)別,如果是LoadBalancerClients注解引入的,會加個default前綴。
在默認(rèn)情況下(引入了nacos-discovery、spring-cloud-loadbalancer的情況下),就會在代碼中如下三處有@LoadBalancerClients注解:
所以,我們工廠創(chuàng)建時debug,可以看到如下場景:
從工廠獲取LoadBalancerLifecycle
上面講完了工廠的創(chuàng)建,這里回到工廠的使用。我們之前看到,會獲取LoadBalancerLifecycle這種bean:
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
但奇怪的是,獲取bean不應(yīng)該先用loadBalancerClientFactory創(chuàng)建的給各個loadBalancerClient的spring容器;再從容器獲取bean嗎?
這里是簡化了,直接讓工廠負(fù)責(zé)全部事務(wù),我要bean的時候,只找工廠要,工廠內(nèi)部自己再去創(chuàng)建spring容器那些。
所以我們看到,工廠是實現(xiàn)了接口:
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);
下面就看看方法如何實現(xiàn)的:
這里就是分了兩步,先獲取容器,再從容器獲取bean。
創(chuàng)建容器
這個獲取容器是先從緩存map獲取,沒有則創(chuàng)建。
我們這里自然是沒有的,進(jìn)入createContext:
這里首先是創(chuàng)建了一個spring上下文,里面是有一個bean容器的,容器里要放什么bean呢,首先就是上圖中的configurations中那些LoadBalancerClient注解里指定的配置類,再然后,就是LoadBalancerClients注解里指定的那些默認(rèn)的配置類,我們這里有3處LoadBalancerClients注解,但是只有nacos那一個,指定了配置類:
@LoadBalancerClients(defaultConfiguration = NacosLoadBalancerClientConfiguration.class)
public class LoadBalancerNacosAutoConfiguration {
所以,這里會把NacosLoadBalancerClientConfiguration這個配置類注冊到容器。
接下來,是如下這行:
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
這里的defaultConfigType是啥呢,其實就是創(chuàng)建工廠時,指定的LoadBalancerClientConfiguration:
到這里為止,基本spring容器該手工放入的bean就這些了。但這個容器內(nèi)到時候只會有這些bean嗎,不是的。
因為我們這里放進(jìn)去的幾個bean,內(nèi)部又定義了更多的bean。
nacosLoadBalancerClientConfiguration
loadBalancerClientConfiguration    
nacosLoadBalancerClientConfiguration
首先是自動裝配一個NacosLoadBalancer(在缺少這種ReactorLoadBalancer bean的情況下)
再下來,會自動裝配ServiceInstanceListSupplier bean:
loadBalancerClientConfiguration
這邊注意,也是在沒注冊這個bean的時候,自動裝配ReactorLoadBalancer,這個其實會和上面的nacos的產(chǎn)生競爭,最終到底是哪個上崗呢,只能看順序了:
和nacos一樣,自動裝配ServiceInstanceListSupplier:
競爭關(guān)系誰勝出
我們上面提到,nacos的配置類和spring-cloud-loadbalancer的配置類,是全面競爭的,最終的話,是誰勝出呢?
我們看看容器完成bean創(chuàng)建后的情況:
可以發(fā)現(xiàn),是nacos的配置贏了。
具體為什么贏,這個暫時不細(xì)說,基本就是bean的order那些事情。反正現(xiàn)在nacos贏了,看起來也沒啥問題,我們就繼續(xù)往后走,目前是完成了bean容器的創(chuàng)建。
獲取LoadBalancerLifecycle類型bean
我這個項目,并沒定義這種bean,所以實際是取不到的,注意的是,在LoadbalancerClient對應(yīng)的容器取不到,還是會去父容器取的。
我們在父容器也沒定義,所以最終是取不到。
根據(jù)服務(wù)名獲取最終實例
loadBalancerClient
目前準(zhǔn)備分析如下代碼:
先看下這個字段來自于哪里:
可以看出,來自于spring容器注入。
所以,這里可以看出,loadBalancerClient類型為BlockingLoadBalancerClient。
loadBalancerClient.choose
進(jìn)入該方法:
最終就是從容器獲取,取到的就是nacos自動裝配的NacosLoadBalancer:
loadBalancer.choose
nacos這里的實現(xiàn)用的反應(yīng)式編程,不怎么了解這塊,反正最終是調(diào)用getInstanceResponse方法,且會把從nacos獲取到的服務(wù)列表傳遞進(jìn)來:
可以看到,這里傳入的就是實際的服務(wù)實例,還包含了nacos相關(guān)的元數(shù)據(jù),如cluster、weight、是否臨時、是否健康等。
后續(xù)的邏輯就根據(jù)實例的各種屬性進(jìn)行篩選,如meta.nacos.cluster、ipv4/ipv6、
根據(jù)權(quán)重進(jìn)行選擇:
根據(jù)實例進(jìn)行feign調(diào)用
我們跟進(jìn)去后,發(fā)現(xiàn)主要就是feignClient.execute進(jìn)行調(diào)用,在前后則是調(diào)用生命周期的相關(guān)方法:
我們看到,這個client就是默認(rèn)的FeignClient,比較原始,直接就是用原生的HttpURLConnection;我們之前文章提到,也是可以使用httpclient、okhttp那些feign.Client的實現(xiàn),只要引入對應(yīng)依賴即可。
另外,這個也是沒有連接池的,每次都是打開新連接;這里也用了外部options參數(shù)中的超時時間。
后面的響應(yīng)處理就略過不講了。
總結(jié)
我們總算是把大體流程都講完了,下一篇講講我遇到的問題。
總結(jié)
以上是生活随笔為你收集整理的Feign源码解析5:loadbalancer的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Text Intelligence -
 - 下一篇: [极客大挑战 2019]LoveSQL