本篇内容:
- OpenFeign與Feign的關系
- Feign底層實作原理
- Ribbon是什麼
- Ribbon底層實作原理
- Ribbon是如何實作失敗重試的?
feign
是
spring cloud
元件中的一個輕量級
restful
的
http
服務用戶端,簡化接口調用,将
http
調用轉為
rpc
調用,讓調用遠端接口像調用同程序應用内的接口調用一樣簡單。
與
dubbo
rpc
遠端調用一樣,通過動态代理實作接口的調用。
feign
通過封裝包裝請求體、發送
http
請求、擷取接口響應結果、序列化響應結果等接口調用動作來簡化接口的調用。
openfeign
則是
spring cloud
在
feign
的基礎上支援了
spring mvc
的注解,如
@RequesMapping
、
@GetMapping
@PostMapping
等。
openfeign
還實作與
Ribbon
的整合。
服務提供者隻需要提供
API
接口,而不需要像
dubbo
那樣需要強制使用
implements
實作接口,使用
fegin
不要求服務提供者在
Controller
使用
implements
關鍵字實作接口。
openfeign
通過包掃描将所有被
@FeignClient
注解注釋的接口掃描出來,并為每個接口注冊一個
FeignClientFactoryBean<T>
執行個體。
FeignClientFactoryBean<T>
是一個
FactoryBean<T>
,當
Spring
調用
FeignClientFactoryBean<T>
getObject
方法時,
openfeign
傳回一個
Feign
生成的動态代理類,攔截方法的執行。
feign
會為代理的接口的每個方法
Method
都生成一個
MethodHandler
。
當為接口上的
@FeignClient
注解的
url
屬性配置服務提供者的
url
時,其實就是不與
Ribbon
整合,由
SynchronousMethodHandler
實作接口方法遠端同步調用,使用預設的
Client
實作類
Default
執行個體發起
http
請求。
當接口上的
@FeignClient
url
屬性不配置時,且會走負載均衡邏輯,也就是需要與
Ribbon
整合使用。這時候不再是使用預設的
Client
(
Default
)調用接口,而是使用
LoadBalancerFeignClient
調用接口(
LoadBalancerFeignClient
也是
Client
接口的實作類,最終還是使用
Default
發起請求),由
LoadBalancerFeignClient
實作與
Ribbon
Ribbon
Netflix
釋出的開源項目,提供在服務消費端實作負載均衡調用服務提供者,從注冊中心讀取所有可用的服務提供者,在用戶端每次調用接口時采用如輪詢負載均衡算法選出一個服務提供者調用,是以,
Ribbon
是一個用戶端負載均衡器。
Ribbon
提供多種負載均衡算法的實作、提供重試支援。
Feign
也提供重試支援,在
SynchronousMethodHandler
invoke
方法中實作,但
Feign
的重試比較簡單,隻是向同一個服務節點發送請求,而
Ribbon
的失敗重試是重新選擇一個服務節點調用的,在服務提供者部署多個節點的情況下,顯然
Feign
的重試機制是沒有多大意義的。
下圖是我在
google
搜出的一道面試題,你們覺得這個答案正确嗎?
Ribbon
Fegin
整合的橋梁是
FeignLoadBalancer
- 1、
會注冊一個Ribbon
(預設使用實作類ILoadBalancer
)負載均衡器,ZoneAwareLoadBalancer
通過Feign
LoadBalancerFeignClient
FeignLoadBalancer
方法來使用executeWithLoadBalancer
Ribbon
負載均衡器選擇一個提供者節點發送ILoadBalancer
請求,實際發送請求還是http
OpenFeign
發起的,FeignLoadBalancer
從始至終都隻負載負載均衡選出一個服務節點。Ribbon
- 2、
的自動配置類會注冊一個spring-cloud-netflix-ribbon
,此RibbonLoadBalancerClient
正是RibbonLoadBalancerClient
為Ribbon
的負載均衡接口提供的實作類,用于實作spring cloud
注解語意。@LoadBalancer
Ribbon
并非直接通過
DiscoveryClient
從注冊中心擷取服務的可用提供者,而是通過
ServerList<Server>
從注冊中心擷取服務提供者,
ServerList
DiscoveryClient
不一樣,
ServerList
不是
Spring Cloud
定義的接口,而是
Ribbon
定義的接口。以
spring-cloud-kubernetes-ribbon
為例,
spring-cloud-kubernetes-ribbon
Ribbon
提供
ServerList
的實作
KubernetesServerList
Ribbon
負責定時調用
ServerList
getUpdatedListOfServers
方法更新可用服務提供者。
怎麼去擷取可用的服務提供者節點由你自己去實作
ServerList
接口,并将實作的
ServerList
注冊到
Spring
容器。如果不提供
ServerList
,那麼使用的将是
Ribbon
提供的預設實作類
ConfigurationBasedServerList
,
ConfigurationBasedServerList
并不會從注冊中心讀取擷取服務節點,而是從配置檔案中讀取。
如果我們使用的注冊中心是
Eureka
,當我們在項目中添加
spring-cloud-starter-netflix-eureka-client
時,其實就已經往項目中導入了一個
ribbon-eureka
jar
,由該
jar
包提供
Ribbon
Eureka
整合所需的
ServerList
:
DiscoveryEnabledNIWSServerList
ribbon-eureka
源碼位址:
https://github.com/Netflix/ribbon/tree/master/ribbon-eureka
,感興趣的朋友可以看下。
public Observable<T> submit(final ServerOperation<T> operation) {
// .......
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
Observable<T> o = (server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(Server server) {
//.......
// 調用相同節點的重試次數
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
// 調用不同節點的重試次數
if (maxRetrysNext > 0 && server == null)
o = o.retry(retryPolicy(maxRetrysNext, false));
return o.onErrorResumeNext(...);
}
複制代碼
private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
return new Func2<Integer, Throwable, Boolean>() {
@Override
public Boolean call(Integer tryCount, Throwable e) {
if (e instanceof AbortExecutionException) {
return false;
}
// 大于最大重試次數
if (tryCount > maxRetrys) {
return false;
}
if (e.getCause() != null && e instanceof RuntimeException) {
e = e.getCause();
}
// 調用RetryHandler判斷是否重試
return retryHandler.isRetriableException(e, same);
}
};
}
複制代碼