SpringCloud OpenFeign整合Ribbon實(shí)現(xiàn)負(fù)載均衡及源碼分析
負(fù)載均衡器在分布式網(wǎng)絡(luò)中扮演著非常重要的角色。通過負(fù)載均衡,可以實(shí)現(xiàn)更好的性能和可靠性,同時(shí)提高系統(tǒng)的可擴(kuò)展性和彈性。目前,SpringCloud體系中,主要使用的有兩種:Netflix的Ribbon以及官方推出的LoadBalancer。本文OpenFeign與Ribbon協(xié)同工作的核心流程及源碼分析。
使用篇
更換內(nèi)置策略
策略UML圖
Ribbon 通過實(shí)現(xiàn)IRule接口,內(nèi)置了多種負(fù)載均衡的策略,默認(rèn)采用的是 RoundRobinRule,即輪詢策略。項(xiàng)目中,服務(wù)節(jié)點(diǎn)的部署可能存在基礎(chǔ)配置、網(wǎng)絡(luò)環(huán)境等的差異,抑或是功能上線需要灰度測試,因此,輪詢策略往往不能滿足實(shí)際需求。
ribbon內(nèi)置策略
通過修改消費(fèi)者工程的配置文件,或修改消費(fèi)者的啟動類或 JavaConfig 類可以實(shí)現(xiàn)更換負(fù)載均衡策略的目的。
方式一,修改配置文件
修改配置文件,在其中添加如下內(nèi)容,指定要使用的負(fù)載均衡策略 <clientName>. <clientConfigNameSpace>.NFLoadBalancerRuleClassName?!咀ⅲ篶lientName是指服務(wù)提供者的服務(wù)名稱】
該方式的好處是,可以為不同的微服務(wù)指定相應(yīng)的負(fù)載均衡策略。
xxx-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
方式二,修改JavaConfig類
在 JavaConfig 類中添加負(fù)載 Bean 方法。全局所有feign對應(yīng)服務(wù)都可以生效。
@Configuration
public class FeignConfiguration {
/**
* 配置隨機(jī)的負(fù)載均衡策略
* 特點(diǎn):對所有的服務(wù)都生效
*/
@Bean
public IRule loadBalancedRule() {
return new RandomRule();
}
}
自定義負(fù)載均衡策略
如果上述的策略不滿足自身要求,我們還可以自定義負(fù)載均衡策略。需要操作 2 個(gè)步驟:
第一步,集成 AbstractLoadBalancerRule類,AbstractLoadBalancerRule實(shí)現(xiàn)了IRule 中的 setLoadBalancer 和 getLoadBalancer,通過繼承 AbstractLoadBalancerRule ,我們就不需要在自己實(shí)現(xiàn)這兩個(gè)方法,而是把關(guān)注點(diǎn)放在choose方法上,即只關(guān)注如何進(jìn)行服務(wù)的負(fù)載上。
/**
* 自定義負(fù)載均衡算法
*/
public class CustomRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// 基本上不需要實(shí)現(xiàn)
}
@Override
public Server choose(Object key) {
// 實(shí)現(xiàn)自己的負(fù)載均衡算法
}
}
第二步,修改JavaConfig類,使用自定義的負(fù)載均衡策略:
@Configuration
public class FeignConfiguration {
@Bean
public IRule loadBalancedRule() {
return new CustomRule();
}
}
或者修改配置文件(application.properties 或 application.yml):
xxx:
ribbon:
NFLoadBalancerRuleClassName: com.xiaopeng.ribbon.CustomRule
Ribbon 核心組件
Ribbon 主要有五大功能組件:ServerList、Rule、Ping、ServerListFilter、ServerListUpdater。
Ribbon 核心組件
(1)負(fù)載均衡器 LoadBalancer
用于管理負(fù)載均衡的組件。初始化的時(shí)候通過加載 YMAL 配置文件創(chuàng)建出來的。
(2)服務(wù)列表 ServerList
ServerList 主要用來獲取所有服務(wù)的地址信息,并存到本地。根據(jù)獲取服務(wù)信息的方式不同,又分為靜態(tài)存儲和動態(tài)存儲。
- 靜態(tài)存儲:從配置文件中獲取服務(wù)節(jié)點(diǎn)列表并存儲到本地。
- 動態(tài)存儲:從注冊中心獲取服務(wù)節(jié)點(diǎn)列表并存儲到本地
(3)服務(wù)列表過濾 ServerListFilter
將獲取到的服務(wù)列表按照過濾規(guī)則過濾。
- 通過 Eureka 的分區(qū)規(guī)則對服務(wù)實(shí)例進(jìn)行過濾。
- 比較服務(wù)實(shí)例的通信失敗數(shù)和并發(fā)連接數(shù)來剔除不夠健康的實(shí)例。
- 根據(jù)所屬區(qū)域過濾出同區(qū)域的服務(wù)實(shí)例。
(4)服務(wù)列表更新 ServerListUpdater
服務(wù)列表更新就是 Ribbon 會從注冊中心獲取最新的注冊表信息。是由這個(gè)接口 ServerListUpdater 定義的更新操作。而它有兩個(gè)實(shí)現(xiàn)類,也就是有兩種更新方式:
- 通過定時(shí)任務(wù)進(jìn)行更新。由這個(gè)實(shí)現(xiàn)類 PollingServerListUpdater 做到的。
- 利用 Eureka 的事件監(jiān)聽器來更新。由這個(gè)實(shí)現(xiàn)類 EurekaNotificationServerListUpdater 做到的。
(5)心跳檢測 Ping
IPing 接口類用來檢測哪些服務(wù)可用。如果不可用了,就剔除這些服務(wù)。實(shí)現(xiàn)類主要有這幾個(gè):PingUrl、PingConstant、NoOpPing、DummyPing、NIWSDiscoveryPing。
心跳檢測策略對象 IPingStrategy,默認(rèn)實(shí)現(xiàn)是輪詢檢測。
(6)負(fù)載均衡策略 Rule
源碼分析
我們知道,如果要使用OpenFeign,需要在項(xiàng)目的啟動類增加@EnableFeignClinets注解,接口定義增加@FeignClient注解,這樣一來,@EnableFeignClinets會掃描出每個(gè)加了@FeignClient注解的接口,然后生成對應(yīng)的BeanDefinition,最后重新生成一個(gè)bean class為FeignClientFactoryBean的BeanDefinition,注冊到spring容器。接下來就會根據(jù)BeanDefinition來生成feign客戶端的代理對象。
Feign代理對象生成過程
生成代理對象后,所有的方法調(diào)用的時(shí)候最終都會走InvocationHandler接口的實(shí)現(xiàn),默認(rèn)就是ReflectiveFeign.FeignInvocationHandler。而我們今天要重點(diǎn)分析的就是,F(xiàn)eignInvocationHandler是如何實(shí)現(xiàn)遠(yuǎn)程及負(fù)載均衡的。
Feign動態(tài)代理調(diào)用實(shí)現(xiàn)rpc流程分析
FeignInvocationHandler對于invoke方法的實(shí)現(xiàn)。
前幾個(gè)if判斷很簡單,就是判斷是不是調(diào)用的方法是不是equals,hashCode,toString,因?yàn)檫@些方法的調(diào)是不需要走rpc調(diào)用的。
接下就是從dispatch獲取要調(diào)用的方法對應(yīng)的MethodHandler,然后調(diào)用MethodHandler的invoke方法。那MethodHandler是什么時(shí)候生成的呢?MethodHandler是在構(gòu)建動態(tài)代理的時(shí)候生成的。那MethodHandler作用是什么呢?你可以理解為最終rpc的調(diào)用都是基于這個(gè)MethodHandler來實(shí)現(xiàn)的,每個(gè)方法都有對應(yīng)MethodHandler來實(shí)現(xiàn)rpc調(diào)用,接下來我們就來看一下MethodHandler的invoke方法的實(shí)現(xiàn)。
MethodHandler是個(gè)接口,有兩個(gè)實(shí)現(xiàn)類,一個(gè)是DefaultMethodHandler,這個(gè)是處理接口中的默認(rèn)方法的,另一個(gè)是SynchronousMethodHandler,這個(gè)是實(shí)現(xiàn)rpc調(diào)用的方法。接下來我們就看看SynchronousMethodHandler關(guān)于invoke方法的實(shí)現(xiàn)。
第一行通過方法的參數(shù)構(gòu)建了一個(gè)RequestTemplate,RequestTemplate可以看成是組裝http請求所需各種參數(shù)的封裝,比如什么情頭,body之類的都放在這里面。
第二行 Options options = findOptions(argv); 這個(gè)很有意思,Options主要是封裝了發(fā)送請求是連接超時(shí)時(shí)間和讀超時(shí)時(shí)間的配置,findOptions(argv)也就是先從參數(shù)里面找有沒有Options,沒有就返回構(gòu)造SynchronousMethodHandler的入?yún)r(shí)的Options,也就是說,連接超時(shí)時(shí)間和讀超時(shí)時(shí)間可以從方法入?yún)韨魅?,不過一般沒有人這么玩。
第三行就是搞一個(gè)重試的組件,是可以實(shí)現(xiàn)重試的,一般不設(shè)置。
然后執(zhí)行到executeAndDecode(template, options),進(jìn)入這個(gè)方法。
首先調(diào)用了targetRequest方法:
這個(gè)方法會遍歷所有的攔截器RequestInterceptor,這是feign的一個(gè)擴(kuò)展點(diǎn),也就說在發(fā)送請求前,仍然還有機(jī)會對請求的內(nèi)容進(jìn)行調(diào)整,比如說加個(gè)請求頭,這也是很常見的一種方式,在微服務(wù)之間鑒權(quán)的時(shí)候使用。RequestInterceptor是在構(gòu)建Feign.Builder的時(shí)候傳進(jìn)來的,F(xiàn)eign.Builder的組件都是通過ioc容器獲取的,組件又是通過配置類來的,所以你需要的話就可以在配置類中聲明RequestInterceptor對象。配置類有不同的優(yōu)先級,按照自己的需求,可以在其中一個(gè)優(yōu)先級使用,不過一般這種通用的東西,不是某個(gè)微服務(wù)特有的功能,一般選擇在springboot啟動中的容器中配置。
執(zhí)行完targetRequest,回到executeAndDecode之后,會構(gòu)建出一個(gè)Request,Request很好理解,就是一個(gè)請求,里面封裝了http請求的東西。接下來就會調(diào)用Client的execute方法來執(zhí)行請求,拿到響應(yīng),接下來就是基于處理這個(gè)響應(yīng),將響應(yīng)數(shù)據(jù)封裝成需要返回的參數(shù),之后返回給調(diào)用方。
到這里,我們已經(jīng)分析出接口的動態(tài)代理是如何運(yùn)行的。其實(shí)就是通過每個(gè)方法對應(yīng)的MethodHandler來實(shí)現(xiàn)的,MethodHandler主要就是拼接各種參數(shù),組裝成一個(gè)請求,隨后交由Client接口的實(shí)現(xiàn)去發(fā)送請求。
LoadBalancerFeignClient
通過上面分析整個(gè)動態(tài)代理調(diào)用過程可以看出,Client是發(fā)送http請求的關(guān)鍵類。那么Client是什么玩意?當(dāng)Feign客戶端在構(gòu)建動態(tài)代理的時(shí)候,填充很多組件到Feign.Builder中,其中有個(gè)組件就是Client的實(shí)現(xiàn),我們并沒有在FeignClientsConfiguration配置類中找到關(guān)于Client的對象的聲明。這個(gè)組件的實(shí)現(xiàn)是要依賴負(fù)載均衡的,也就是這個(gè)組件是Feign用來整合Ribbon的入口。
接下來,我們就著重看一下Client的實(shí)現(xiàn),看看Feign是如何通過ribbon實(shí)現(xiàn)負(fù)載均衡的。
我們先來看一下Feign跟ribbon整合的配置類。
我們來分析一下,首先通過@Impot注解導(dǎo)入了三個(gè)配置類。
- HttpClientFeignLoadBalancedConfiguration:基于HttpClient實(shí)現(xiàn)http調(diào)用的。
- OkHttpFeignLoadBalancedConfiguration:基于OkHttp實(shí)現(xiàn)http調(diào)用的。
- DefaultFeignLoadBalancedConfiguration:默認(rèn)的,也就是Feign原生的發(fā)送http的實(shí)現(xiàn)。
這里我們看一下DefaultFeignLoadBalancedConfiguration配置類,因?yàn)槟J(rèn)就是它,HttpClientFeignLoadBalancedConfiguration和OkHttpFeignLoadBalancedConfiguration都需要有引入HttpClient和OkHttp依賴才會有用。
這個(gè)配置類很簡單,聲明了LoadBalancerFeignClient到spring容器,傳入了三個(gè)參數(shù),一個(gè)Client的實(shí)現(xiàn),一個(gè)
CachingSpringLoadBalancerFactory和一個(gè)SpringClientFactory。LoadBalancerFeignClient這個(gè)類實(shí)現(xiàn)了Client接口,也就數(shù)說我們在構(gòu)建Feign.Builder填充的就是這個(gè)對象,也就是上面說feign的執(zhí)行流程最后用來執(zhí)行請求的Client的實(shí)現(xiàn)。
接下來我說一下入?yún)⒌娜齻€(gè)參數(shù)是什么意思。
- Client.Default:就是Feign自己實(shí)現(xiàn)的Client,里面封裝了真正發(fā)送http發(fā)送請求的功能,LoadBalancerFeignClient雖然也實(shí)現(xiàn)了Client接口,但是這個(gè)實(shí)現(xiàn)其實(shí)是為了整合Ribbon用的,并沒有發(fā)送http的功能,所以需要有個(gè)可以發(fā)送http功能的實(shí)現(xiàn)。
- CachingSpringLoadBalancerFactory:后面會說這個(gè)類的作用
- SpringClientFactory:這個(gè)跟Feign里面的FeignContext的作用差不多,用來實(shí)現(xiàn)配置隔離的,當(dāng)然,這個(gè)也在關(guān)于Ribbon的那篇文章有剖析過。
- 其實(shí)大家可以自行去看OkHttpFeignLoadBalancedConfiguration和HttpClientFeignLoadBalancedConfiguration,其實(shí)他們配置跟DefaultFeignLoadBalancedConfiguration是一樣的,聲明的對象都是LoadBalancerFeignClient,只不過將Client.Default換成了基于HttpClient和OkHttp的實(shí)現(xiàn),也就是發(fā)送http請求使用的工具不一樣。
FeignRibbonClientAutoConfiguration除了導(dǎo)入配置類還聲明了CachingSpringLoadBalancerFactory,只不過一種是帶基于spring實(shí)現(xiàn)的重試功能的,一種是不帶的,主要看有沒有引入spring重試功能的包,所以上面構(gòu)建LoadBalancerFeignClient注入的CachingSpringLoadBalancerFactory就是在這聲明的。
這里就說完了Feign整合ribbon的配置類
FeignRibbonClientAutoConfiguration,我們也找到了構(gòu)造Feign.Builder的實(shí)現(xiàn)LoadBalancerFeignClient,接下來就來剖析LoadBalancerFeignClient的實(shí)現(xiàn)。
在動態(tài)代理調(diào)用的那里我們得出一個(gè)結(jié)論,那就是最后會調(diào)用Client接口的execute方法的實(shí)現(xiàn),所以我們就看一下execute方法的實(shí)現(xiàn),這里就是一堆操作,從請求的URL中拿到了clientName,也就是服務(wù)名。
為什么可以拿到服務(wù)名?
其實(shí)很簡單,OpenFeign構(gòu)建動態(tài)代理的時(shí)候,傳入了一個(gè)HardCodedTarget,當(dāng)時(shí)說在構(gòu)建HardCodedTarget的時(shí)候傳入了一個(gè)url,那個(gè)url當(dāng)時(shí)說了其實(shí)就是http://服務(wù)名,所以到這里,雖然有具體的請求接口的路徑,但是還是類似 http://服務(wù)名/api/sayHello這種,所以可以通過路徑拿到你鎖請求的服務(wù)名。
拿到服務(wù)名之后,再拿到了一個(gè)配置類IClientConfig,最后調(diào)用lbClient,我們看一下lbClient的方法實(shí)現(xiàn)。
就是調(diào)用CachingSpringLoadBalancerFactory的create方法。
這個(gè)方法先根據(jù)服務(wù)名從緩存中獲取一個(gè)FeignLoadBalancer,獲取不到就創(chuàng)建一個(gè)。
創(chuàng)建的過程就是從每個(gè)服務(wù)對應(yīng)的容器中獲取到IClientConfig和ILoadBalancer。Ribbon那篇文章都講過這些核心類,這里不再贅述。
默認(rèn)就是創(chuàng)建不帶spring重試功能的FeignLoadBalancer,放入緩存,最后返回這個(gè)FeignLoadBalancer。所以第一次來肯定沒有,需要構(gòu)建,也就是最終一定會返回FeignLoadBalancer,所以我們通過lbClient方法拿到的是FeignLoadBalancer。從這里可以看出
CachingSpringLoadBalancerFactory是構(gòu)建FeignLoadBalancer的工廠類,只不過先從緩存中查找,找不到再創(chuàng)建FeignLoadBalancer。
拿到FeignLoadBalancer之后就會調(diào)用executeWithLoadBalancer,接收到Response之后直接返回。
FeignLoadBalancer
那么這個(gè)FeignLoadBalancer又是啥呢?這里放上FeignLoadBalancer核心源碼。
FeignLoadBalancer繼承自AbstractLoadBalancerAwareClient,AbstractLoadBalancerAwareClient又是啥玩意?看過我寫的關(guān)于Ribbon核心組件已經(jīng)運(yùn)行原理的那篇文章小伙伴肯定知道,AbstractLoadBalancerAwareClient類主要作用是通過ILoadBalancer組件獲取一個(gè)Server,然后基于這個(gè)Server重構(gòu)了URI,也就是將你的請求路徑http://服務(wù)名/api/sayHello轉(zhuǎn)換成類似http://192.168.1.101:8088/api/sayHello這種路徑,也就是將原服務(wù)名替換成服務(wù)所在的某一臺機(jī)器ip和端口,替換之后就交由子類實(shí)現(xiàn)的exceut方法來發(fā)送http請求。
所以我們知道調(diào)用executeWithLoadBalancer之后,就會重構(gòu)請求路徑,將服務(wù)名替換成某個(gè)具體的服務(wù)器所在的ip和端口,之后交給子類execute來處理,對于這里來說,也就是FeignLoadBalancer的execute方法,因?yàn)镕eignLoadBalancer繼承
AbstractLoadBalancerAwareClient。
直接定位到execute方法最核心的一行代碼。
request.client()就會拿到構(gòu)建LoadBalancerFeignClient傳入的那個(gè)Client的實(shí)現(xiàn),我提到過,這個(gè)Client的實(shí)現(xiàn)是具體發(fā)送請求的實(shí)現(xiàn),默認(rèn)的就是Client.Default類(不是默認(rèn)就有可能是基于HttpClient或者是OkHttp的實(shí)現(xiàn))。所以這行代碼就是基于這個(gè)Client就成功的發(fā)送了Http請求,拿到響應(yīng),然后將這個(gè)Response 封裝成一個(gè)RibbonResponse返回,最后就返回給MethodHandler,然后解析響應(yīng),封裝成方法的返回值返回給調(diào)用者。
好了,其實(shí)到這里就完全知道Feign是如何整合Ribbon的,LoadBalancerFeignClient其實(shí)是OpenFeign適配Ribbon的入口,F(xiàn)eignLoadBalancer才是真正實(shí)現(xiàn)選擇負(fù)載均衡,發(fā)送http請求的組件,因?yàn)樗^承了
AbstractLoadBalancerAwareClient。
整個(gè)動態(tài)代理的調(diào)用過程:
通過這張圖,我們可以清楚地看出OpenFeign、Ribbon以及注冊中心之間的協(xié)同關(guān)系。
總結(jié)
OpenFeign在進(jìn)行rpc調(diào)用的時(shí)候,由于不知道服務(wù)具體在哪臺機(jī)器上,所以需要Ribbon這個(gè)負(fù)載均衡組件從服務(wù)所在的機(jī)器列表中選擇一個(gè),Ribbon中服務(wù)所在的機(jī)器列表是從注冊中心拉取的,Ribbon提供了一個(gè)ServerList接口,注冊中心實(shí)現(xiàn)之后,Ribbon就可以獲取到服務(wù)所在的機(jī)器列表,這就是這三個(gè)組件最基本的原理。
參考&鳴謝
https://www.cnblogs.com/zzyang/p/16404783.html