天天看點

OpenFeign與Ribbon源碼分析總結與面試題

本篇内容:

  • 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

搜出的一道面試題,你們覺得這個答案正确嗎?

OpenFeign與Ribbon源碼分析總結與面試題

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

OpenFeign與Ribbon源碼分析總結與面試題

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);
            }
        };
    }
複制代碼           

繼續閱讀