SpringCloud升級之路2020.0.x版-37. 實現非同步的客戶端封裝配置管理的意義與設計

乾貨滿滿張雜湊發表於2021-11-19

本系列程式碼地址:https://github.com/JoJoTec/spring-cloud-parent

為何需要封裝非同步 HTTP 客戶端 WebClient

對於同步的請求,我們使用 spring-cloud-openfeign 封裝的 FeignClient,並做了額外的定製。對於非同步的請求,使用的是非同步 Http 客戶端即 WebClient。WebClient 使用也比較簡單,舉一個簡單的例子即:

//使用 WebClient 的 Builder 建立 WebClient
WebClient client = WebClient.builder()
  //指定基址
  .baseUrl("http://httpbin.org")
  //可以指定一些預設的引數,例如預設 Cookie,預設 HttpHeader 等等
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  .build();

建立好 WebClient 後即可以使用這個 WebClient 進行呼叫:

// GET 請求 /anything 並將 body 轉化為 String
Mono<String> stringMono = client.get().uri("/anything").retrieve().bodyToMono(String.class);
//這裡為了測試,採用阻塞獲取
String block = stringMono.block();

返回的結果如下所示(請求 http://httporg.bin/anything 會將請求中的所有內容原封不動返回,從這裡我們可以看出上面測試的 Header 還有 cokkie 都被返回了):

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Cookie": "TestCookie=TestCookieValue,getAnythingCookie=getAnythingCookieValue", 
    "Getanythingheader": "getAnythingHeaderValue", 
    "Host": "httpbin.org", 
    "Testheader": "TestHeaderValue", 
    "User-Agent": "ReactorNetty/1.0.7"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "12.12.12.12", 
  "url": "http://httpbin.org/anything"
}

我們也可以加入負載均衡的功能,讓 WebClient 利用我們內部的 LoadBalancer,負載均衡呼叫其他微服務,首先注入負載均衡 Filter:

@Autowired
ReactorLoadBalancerExchangeFilterFunction lbFunction;

建立 WebClient 的時候,將這個 Filter 加入:

//使用 WebClient 的 Builder 建立 WebClient
WebClient client = WebClient.builder()
  //指定基址微服務
  .baseUrl("http://微服務名稱")
  //可以指定一些預設的引數,例如預設 Cookie,預設 HttpHeader 等等
  .defaultCookie("cookieKey", "cookieValue")
  .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
  //負載均衡器,改寫url
  .filter(lbFunction)
  .build();

這樣,這個 WebClient 就能呼叫微服務了。

但是,這樣還不能滿足我們的需求:

  1. 需要在 WebClient 加入像 Feignclient 裡面加的類似的重試與斷路機制,執行緒隔離就不需要了,因為都是非同步請求不會阻塞任務執行緒。
  2. 需要針對不同的微服務配置不同的連線超時以及響應超時來適應不同微服務。
  3. 這些配置都增加了程式碼的複雜度,我們需要減少這些程式碼對於業務的侵入性,最好能通過純配置實現這些 WebClient 的初始化。

要實現的配置設計以及使用舉例

首先,我們要實現的 WebClient,其 Filter 包含三個:

  1. 重試 Filter:重試的 Filter 要在負載均衡 Filter 之前,因為重試的時候,我們會從負載均衡器獲取另一個例項進行重試,而不是在同一個例項上重試多次
  2. 負載均衡 Filter,其實就是內建的 ReactorLoadBalancerExchangeFilterFunction
  3. 斷路器 Filter:斷路器需要在負載均衡之後,因為只有負載均衡之後才能拿到具體本地呼叫的服務例項,這樣我們才能實現基於微服務例項方法級別的斷路器

需要重試的場景:

  1. 非 2xx 的響應碼返回,並且方法是可以重試的方法。如何定義方法是可以重試的,首先 GET 方法是可以重試的,對於其他方法,根據配置中的是否配置了這個 URL 可以重試決定
  2. 異常重試
    1. 連線異常:例如連線超時,連線中斷等等,所有請求的連線異常都可以重試,因為請求並沒有發出去。
    2. 斷路器異常:該服務例項方法級別的斷路器開啟了,需要直接重試其他例項,因為請求並沒有發出去。
    3. 響應超時異常:這個重試邏輯和非 2xx 的響應碼返回一樣。

我們需要實現的配置方式是,通過這樣配置 application.yml

webclient:
  configs:
    //微服務名稱
    testService1:
      //請求基址,第一級域名作為微服務名稱
      baseUrl: http://testService1
      //最多的 http 連線數量
      maxConnection: 50
      //連線超時
      connectTimeout: 500ms
      //響應超時
      responseTimeout: 60s
      //除了 GET 方法外,哪些路徑還能重試
      retryablePaths:
        - /retryable/**
        - /user/orders

加入這些配置,我們就能獲取載對應微服務的 WebClient 的 Bean,例如:

//自動裝載 我們自定義的 WebClient 的 NamedContextFactory,這個是我們後面要實現的
@Autowired
private WebClientNamedContextFactory webClientNamedContextFactory;


//通過微服務名稱,獲取對應的微服務呼叫的 WebClient
webClientNamedContextFactory.getWebClient("testService1");

接下來,我們會實現這些。

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

相關文章