偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

強(qiáng)悍的重試機(jī)制:Spring Boot 中 WebClient 彈性設(shè)計(jì)實(shí)戰(zhàn)

開發(fā) 前端
Spring WebFlux 包含一個(gè)用于執(zhí)行 HTTP 請求的客戶端。WebClient 具有基于 Reactor 的功能性流暢 API,它可以聲明式地組成異步邏輯,而無需處理線程或并發(fā)問題。它是完全無阻塞的,支持流式傳輸。

環(huán)境:SpringBoot3.4.2

1. 簡介

Spring WebFlux 包含一個(gè)用于執(zhí)行 HTTP 請求的客戶端。WebClient 具有基于 Reactor 的功能性流暢 API,它可以聲明式地組成異步邏輯,而無需處理線程或并發(fā)問題。它是完全無阻塞的,支持流式傳輸。

在分布式系統(tǒng)中,網(wǎng)絡(luò)請求可能因臨時(shí)故障(如超時(shí)、服務(wù)不可用、限流)而失敗。合理的重試機(jī)制能提升系統(tǒng)韌性,但不同 HTTP 客戶端的實(shí)現(xiàn)方式差異顯著。RestTemplate 和 RestClient 都需要通過自定義攔截器(ClientHttpRequestInterceptor)或是借助 Spring Retry 庫實(shí)現(xiàn)重試機(jī)制,開發(fā)者需手動(dòng)處理異常類型、重試次數(shù)、退避策略等細(xì)節(jié),代碼冗余且易出錯(cuò)。而 WebClient 內(nèi)置了響應(yīng)式重試機(jī)制,通過 retryWhen 操作符與 RetryBackoffSpec 組合,可聲明式地定義重試規(guī)則,無需編寫攔截器或引入額外依賴。這種設(shè)計(jì)不僅簡化了代碼,還天然適配異步非阻塞場景

接下來,我們將詳細(xì)的介紹WebClient的重試機(jī)制。

2.實(shí)戰(zhàn)案例

2.1 基本使用

默認(rèn)情況下,WebClient 實(shí)例不會(huì)自動(dòng)執(zhí)行任何重試操作,除非你主動(dòng)添加相關(guān)操作。當(dāng)你調(diào)用其他服務(wù)時(shí),如果發(fā)生超時(shí)或拋出錯(cuò)誤,請求會(huì)直接失敗,并將錯(cuò)誤沿響應(yīng)式鏈(reactive chain)傳遞下去。若要實(shí)現(xiàn)重試功能,你需要在數(shù)據(jù)流中添加一個(gè)重試操作符(retry operator)。

最直接的重試方式是使用 .retry(n) 方法,其中 n 表示首次失敗后允許的最大重試次數(shù)。如下示例:

// 基本配置
HttpClient httpClient = HttpClient.create()
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000);
WebClient webClient = WebClient.builder()
    .baseUrl("http://localhost:9999")
    .clientConnector(new ReactorClientHttpConnector(httpClient))
    .build() ;
// 調(diào)用遠(yuǎn)程調(diào)用
webClient.get()
  .uri("/api/query")
  .retrieve()
  .bodyToMono(String.class)
  .doOnError(err -> {
    System.err.printf("發(fā)生錯(cuò)誤: %s%n", err.getMessage()) ;
  })
  .retry(2)
  .subscribe(System.out::println, error -> System.err.printf("請求失敗: %s%n", error.getMessage()));

這種方法添加簡單,但存在一個(gè)問題:它會(huì)立即連續(xù)重試,不給遠(yuǎn)程系統(tǒng)任何恢復(fù)時(shí)間。對于偶發(fā)的網(wǎng)絡(luò)抖動(dòng)(network flukes),這種策略可能有效;但如果問題是由服務(wù)過載(heavy load)引起的,連續(xù)重試反而可能加劇系統(tǒng)壓力。

注意:每次重試都會(huì)在前一次請求剛結(jié)束時(shí)立即啟動(dòng)。這意味著,如果被調(diào)用的服務(wù)已經(jīng)處于高負(fù)載狀態(tài),這種連續(xù)重試只會(huì)進(jìn)一步加劇系統(tǒng)壓力??傆?jì)3次。

圖片圖片

下面是關(guān)于retry方法執(zhí)行原理:

圖片圖片

2.2 重試添加退避規(guī)則

重試調(diào)用在它們之間留有間隔時(shí)效果會(huì)更好。再次嘗試前給系統(tǒng)一個(gè)短暫的時(shí)間。這個(gè)間隔可以保持不變,也可以每次逐漸延長。我們可以使用 Retry.backoff 與 retryWhen 結(jié)合,這樣能做更多控制權(quán),并且更符合 Reactor 處理重試的方式。并且可以決定暫停多久以及允許嘗試多少次。如下示例:

WebClient webClient = ... ;


webClient.get()
  .uri("/api/query")
  .retrieve()
  .bodyToMono(String.class)
  .doOnError(err -> {
    System.err.printf("發(fā)生錯(cuò)誤: %s - %s%n", DateTimeFormatter.ofPattern("mm:ss")
        .format(LocalDateTime.now()), err.getMessage()) ;
  })
  .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
  .subscribe(System.out::println, error -> System.err.printf("請求失敗: %s%n", error.getMessage()));

輸出結(jié)果

圖片圖片

首次重試間隔為1s,之后是3s。這也給了目標(biāo)服務(wù)恢復(fù)的時(shí)間。

2.3 自定義重試機(jī)制

并非所有失敗都值得重試。有些錯(cuò)誤是暫時(shí)的,有些則無法自行恢復(fù)。例如:

  • 404 表示請求的資源不存在
  • 400 通常表示請求格式錯(cuò)誤

這類錯(cuò)誤無需重試,因?yàn)橹貜?fù)嘗試只會(huì)得到相同結(jié)果。但 500 服務(wù)器錯(cuò)誤 或 超時(shí) 可能意味著服務(wù)只需多一秒即可恢復(fù),此時(shí)重試才有意義。

通過 Retry 構(gòu)建器,你可以配置一個(gè)過濾器,明確指定哪些失敗需要重試、哪些應(yīng)直接跳過。如下示例:

Retry retryStrategy = Retry.fixedDelay(2, Duration.ofMillis(500))
  // 過濾,值對500以上的錯(cuò)誤碼進(jìn)行重試
  .filter(throwable -> {
    if (throwable instanceof WebClientResponseException) {
      int status = ((WebClientResponseException) throwable).getStatusCode().value() ;
      return status >= 500;
    }
    return throwable instanceof WebClientRequestException;
  }) ;


webClient.get()
  .uri("/api/query")
  .retrieve()
  .bodyToMono(String.class)
  .doOnError(err -> {
    System.err.printf("發(fā)生錯(cuò)誤: %s - %s%n", DateTimeFormatter.ofPattern("mm:ss")
        .format(LocalDateTime.now()), err.getMessage()) ;
  })
  .retryWhen(retryStrategy)
  .subscribe(System.out::println, error -> System.err.printf("請求失敗: %s%n", error.getMessage()));

當(dāng)發(fā)生小于500錯(cuò)誤時(shí),輸出如下:

圖片圖片

當(dāng)發(fā)生大于等于500錯(cuò)誤時(shí),輸出如下:

圖片圖片

2.4 避免重試風(fēng)暴

重試機(jī)制若使用不當(dāng)易引發(fā) "重試風(fēng)暴":當(dāng)大量服務(wù)無延遲地連續(xù)重試失敗請求時(shí),會(huì)向已承壓的下游系統(tǒng)爆發(fā)式涌入流量,導(dǎo)致其崩潰并擴(kuò)散至整個(gè)系統(tǒng)。避免風(fēng)暴的關(guān)鍵在于退避(Backoff)與抖動(dòng)(Jitter):退避通過逐步延長重試間隔降低負(fù)載,抖動(dòng)則引入隨機(jī)性防止請求周期性對齊。如下示例:

Retry retryWithJitter = Retry.backoff(4, Duration.ofMillis(500))
    .jitter(0.8)
    .filter(throwable -> {
      if (throwable instanceof WebClientResponseException) {
        int status = ((WebClientResponseException) throwable).getStatusCode().value() ;
        return status >= 500;
      }
      return throwable instanceof WebClientRequestException;
    }) ;
webClient.get()
  .uri("/api/query")
  .retrieve()
  .bodyToMono(String.class)
  .doOnError(err -> {
    System.err.printf("發(fā)生錯(cuò)誤: %s - %s%n", DateTimeFormatter.ofPattern("mm:ss")
        .format(LocalDateTime.now()), err.getMessage()) ;
  })
  .retryWhen(retryWithJitter)
  .subscribe(System.out::println, error -> System.err.printf("請求失敗: %s%n", error.getMessage()));

輸出結(jié)果

圖片圖片

此處設(shè)置的抖動(dòng)范圍為 80%,即每次重試的延遲時(shí)間會(huì)在基準(zhǔn)值基礎(chǔ)上隨機(jī)增減一定比例。這種微小的時(shí)序偏移能避免所有重試請求在同一時(shí)刻集中涌向后端系統(tǒng)。

2.5 重試最終兜底 - 熔斷

當(dāng)重試徹底失效時(shí),需立即停止請求以避免系統(tǒng)雪崩,此時(shí)熔斷器(Circuit Breaker)便派上用場。它會(huì)持續(xù)監(jiān)測失敗次數(shù),一旦達(dá)到閾值,便臨時(shí)阻斷新請求,為下游服務(wù)爭取恢復(fù)時(shí)間。

Spring Boot 的 WebClient 本身未內(nèi)置熔斷功能,但可無縫集成 Spring Cloud Circuit Breaker 或 Resilience4j。通過熔斷器包裝 WebClient 邏輯,可在故障持續(xù)時(shí)提前攔截請求,防止其涌向已崩潰的下游系統(tǒng)。如下示例:

首先,引入依賴
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
  <version>3.3.0</version>
</dependency>

配置文件

resilience4j:
  circuitbreaker:
    # 此種方式適用于使用CircuitBreakerFactory方式
    configs:
      order-service:
        minimum-number-of-calls: 1
        failure-rate-threshold: 10
        wait-duration-in-open-state: 10s

示例代碼

private final ReactiveCircuitBreakerFactory<?, ?> rcbFactory ;


public Mono<String> invoke() {
  return this.rcbFactory.create("order-service").run(webClient.get()
    .uri("/api/query")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(err -> {
      System.err.printf("發(fā)生錯(cuò)誤: %s - %s%n", DateTimeFormatter.ofPattern("mm:ss")
          .format(LocalDateTime.now()), err.getMessage()) ;
    }), ex -> Mono.just("fallback response")) ;
}

運(yùn)行結(jié)果

圖片圖片

責(zé)任編輯:武曉燕 來源: Springboot全家桶實(shí)戰(zhàn)案例
相關(guān)推薦

2021-02-20 10:02:22

Spring重試機(jī)制Java

2022-05-06 07:44:10

微服務(wù)系統(tǒng)設(shè)計(jì)重試機(jī)制

2025-02-26 10:49:14

2022-11-14 08:19:59

重試機(jī)制Kafka

2025-04-18 03:00:00

2025-09-10 07:15:45

2024-01-04 18:01:55

高并發(fā)SpringBoot

2024-09-25 08:32:05

2024-11-21 14:42:31

2020-07-19 15:39:37

Python開發(fā)工具

2025-01-03 08:44:37

kafka消息發(fā)送策略

2023-10-27 08:20:12

springboot微服務(wù)

2017-07-02 16:50:21

2017-06-16 15:16:15

2023-11-27 07:44:59

RabbitMQ機(jī)制

2025-09-01 07:40:59

2023-11-06 08:00:38

接口高可用機(jī)制

2025-05-28 01:15:00

Golang重試機(jī)制

2025-02-26 08:10:40

2025-02-27 09:35:22

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號