Feign源碼解析4:調用過程

背景

前面幾篇分析了Feign的初始化過程,歷經艱難,可算是把@FeignClient註解的接口對應的代理對象給創建出來了。今天看下在實際Feign調用過程中的一些源碼細節。

我們這裏Feign接口如下:

@FeignClient(value = "echo-service-provider") // 指向服務提供者應用
public interface EchoService {

    @GetMapping("/echo/{message}")
    String echo(@PathVariable("message") String message);
}

調用代碼:

http://localhost:8080/feign/echo/ddd

@GetMapping("/feign/echo/{message}")
public String feignEcho(@PathVariable String message) {
    return echoService.echo(message);
}

完整解析

動態代理

這裏的echoService此時的類型其實是jdk動態代理,內部有一個字段,就是實現了jdk的InvocationHandler接口:

image-20240111195852703

實際邏輯如下,會根據被調用的method找到一個合適的handler:

import feign.InvocationHandlerFactory.MethodHandler

static class FeignInvocationHandler implements InvocationHandler{
	private final Map<Method, MethodHandler> dispatch;
}    

image-20240111200203547

一般獲取到的都是如下類型:

image-20240111200454268

這個類有如下核心屬性:

image-20240111200633673

都是和http請求息息相關的,如重試、請求攔截器、響應攔截器、logger、logger級別、options(包含了超時時間等參數)。

重試

接下來看實際請求的大體框架:

image-20240111201801576

上面主要是一個while循環,內部會執行請求,如果請求報錯,拋出了RetryableException類型的異常,此時就會由重試組件(Retryer retryer),判斷是否要重試,如果要的話,就會continue,就會再次執行請求。

但如果重試組件認爲不需要重試或重試次數已經超過,就會拋出異常,此時就走不到continue部分了,會直接向上層拋異常。

image-20240111202202423

注意,這個重試接口實現了Cloneable,因爲每次請求的時候,都要有一個對應的重試對象來記錄當前請求的重試狀態(比如重試了幾次了),正因爲有狀態,所以得每次clone一個新的。

Retryer retryer = this.retryer.clone();

默認情況下,不做任何配置,都是不重試的,此時的retryer類型爲一個內部類,意思是NEVER_RETRY:

image-20240111202629257

這裏再拓展一點,什麼情況下,Feign會拋出RetryableException呢?

其實就是在執行上圖的正常執行部分,遇到了java.io.IOException的時候,就會拋這種RetryableException。

image-20240111202902797

  static FeignException errorExecuting(Request request, IOException cause) {
    return new RetryableException(
        -1,
        format("%s executing %s %s", cause.getMessage(), request.httpMethod(), request.url()),
        request.httpMethod(),
        cause,
        null, request);
  }

還有一種情況是啥呢?是服務端返回的http header中包含了Retry-After這個header的時候:

image-20240111203152695

這個header一般是在503的時候返回:

image-20240111203450457

根據模版創建請求

大家看看我們的接口,用了path variable,所以,在真正進行請求前,必須先完成一些準備工作,比如把path variable替換爲真實的值:

@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message);

image-20240111203856466

另外,大家看代碼,還有個參數傳給下層:

image-20240111203951747

image-20240111204006223

這個就是控制請求超時參數的。這個options從哪裏來呢,從我們的傳參來。我們可以在方法中加一個參數:

@GetMapping("/echo/{message}")
String echo(@PathVariable("message") String message, Request.Options options);

這個的優先級,我覺得應該是最高的。

這樣就能支持,每個接口用不一樣的超時時間。

executeAndDecode概覽

生成絕對路徑請求

image-20240111204351111

先說說1處:

Request targetRequest(RequestTemplate template) {
    // 使用請求攔截器
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    return target.apply(template);
}

請求攔截器的類型:

public interface RequestInterceptor {

  /**
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

這裏是對模版進行修改,我看註釋,有如下場景(增加全局的header):

image-20240111204640939

接下來,再看看模版如何轉化爲請求:

return target.apply(template);

image-20240111205643793

結果,其實也沒幹啥,就是模版裏只有接口的相對路徑,此處要拼接爲完整路徑:

image-20240111205735846

client.execute接口

實際執行請求,靠client對象,它的默認類型爲:

image-20240111210008513

它是自動裝配的:

image-20240111210100724

它內部包裹了實際發http請求的庫,上面的代碼中,默認用的是feign自帶的(位於feign-core依賴):

new Client.Default(null, null)

image-20240111210613212

比如,假設我們想用httpclient (在classpath中包含),就會觸發自動裝配:

image-20240111210835078

image-20240111210925581

也支持okhttp(feign.okhttp.enabled爲true):

image-20240111210254516

這裏看看FeignBlockingLoadBalancerClient的幾個構造參數:

public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {
    this.delegate = delegate;
    this.loadBalancerClient = loadBalancerClient;
    this.loadBalancerClientFactory = loadBalancerClientFactory;
}

第一個是上面提到的http客戶端,第二個、第三個呢,其實是負責負載均衡的客戶端(不只是要負責客戶端負載均衡算法,還要負責從nacos這些地方獲取服務對應的實例)

image-20240111211410846

client獲取服務實例

這個execute方法實際有兩部分,一部分是如下,根據service名稱,獲取服務實例(包含了真實的ip、端口),再一部分纔是對服務實例發起請求。

下面的1處,是獲取一些listener,然後通知listener,我們已經開始請求了,這裏的listener的類型是:

org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle

包含了好些個生命週期方法,如onStart方法:

void onStart(Request<RC> request);

image-20240111212137223

然後是2處,利用loadBalancerClient,根據服務名,如我們這裏的echo-service-provider,獲取到一個實例(類型爲org.springframework.cloud.client.ServiceInstance)。

3處會判斷,如果沒獲取到實例,此時就會報503了,服務實例不存在。

這裏面,獲取實例的部分,比較複雜,我們得單獨開一篇來講。

發起真實請求

根據服務實例,組裝真實的url進行請求。

image-20240111213552767

這個等負載均衡部分寫完了,再講解這部分,這塊的邏輯也還好,無非是對httpclient這些的封裝。

總結

我們把整體的feign調用的脈絡梳理了一遍,下篇繼續loadbalancer的部分,那裏也是有點坑的。

用講師的話來結個尾:啥也不是,散會!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章