本文是精講響應式WebClient第6篇,前篇的blog訪問地址如下:
- 精講響應式webclient第1篇-響應式非阻塞IO與基礎用法
- 精講響應式WebClient第2篇-GET請求阻塞與非阻塞呼叫方法詳解
- 精講響應式WebClient第3篇-POST、DELETE、PUT方法使用
- 精講響應式WebClient第4篇-檔案上傳與下載
- 精講響應式WebClient第5篇-請求超時設定與異常處理
在上一篇我們為大家介紹了WebClient的異常處理方法,我們可以對指定的異常進行處理,也可以分類處理400-499、500-599狀態碼的HTTP異常。
我們本節為大家介紹的實際上是另外一種異常處理機制:請求失敗之後自動重試。當WebClient發起請求,沒有得到正常的響應結果,它就會每隔一段時間再次傳送請求,可以傳送n次,這個n是我們自定義的。n次請求都失敗了,最後再將異常丟擲,可以通過我們上一節交給大家的方法進行異常處理。也就是針對連線超時異常、讀寫超時異常等,或者是HTTP響應結果為非正常狀態碼(不是200狀態碼段),都在自動重試機制的範疇內。
如果您覺得我的文章對您有幫助的話,請幫忙點贊或分享,您的支援是我不竭的創作動力!
一、請求異常重試
下面的程式碼是請求"http://jsonplaceholder.typicode.com" 網站的服務,該網站是一個免費提供HTTP請求測試的服務端網站,我們可以用它測試WebClient。需要注意的是:正常的GET方法請求地址是"/posts/1",我特意的把它寫錯成為"/postss/1",這樣可以觸發404資源無法找到的異常。
public class ReTryTest {
@Test
public void testRetry() {
WebClient webClient = WebClient.builder()
.baseUrl("http://jsonplaceholder.typicode.com")
.build();
Mono<String> mono = webClient
.get() //GET 請求
.uri("/postss/1") // 請求路徑,注意為了製造異常,這裡是錯的
.retrieve() //獲取請求結果
.bodyToMono(String.class) //用Mono接收單個非集合物件資料
.doOnError(Exception.class, err -> { //處理異常
System.out.println(LocalDateTime.now() + "---發生錯誤:" +err.getMessage() );
})
.retry(3);
System.out.println("=====" + mono.block());
}
}
- doOnError異常處理是我們在上一節文章中為大家介紹的異常處理函式,我們在這裡列印日誌,觀察重試次數
- retry(3)就是重點了,表示請求失敗之後重試3次請求。也可以使用retry()無參方法,不設定次數,可以無限重試。這樣顯然不好,我們一般不用。
下面是doOnError中列印的控制檯輸出內容,一共列印了4次。(一次失敗 + 三次重試失敗)
二、重試時間間隔設定
上面的請求重試方法,請求失敗之後立即重試,在很短的時間內就完成了3次重試。如果這是在生產環境下,可能你的服務端因為資源緊張造成請求響應超時等異常,這種重試機制無疑會讓本就不堪重負的服務端雪上加霜。我們下面交給大家一種為重試設定時間間隔的方法:
.retryBackoff(3, Duration.ofSeconds(5));
- 第一個引數仍然表示重試3次
- 第二個參數列示按指數增長的時間間隔重試,第一次重試間隔5秒,第二次間隔10秒(5 x2),第三次間隔20秒(5x2x2)
原始碼如下:
三、retryWhen方法
上面的retryBackoff方法雖然已經一定程度上緩解了請求重試導致的服務端的壓力,但是它還是不分場景的不斷重試。
- 在實際的開發中,可以請求重試的場景應該是:網路異常、請求超時異常、服務端突然面臨高併發導致的臨時處理能力不足導致的超時等這種由於外部原因導致的異常場景。
- 對於那些由於程式設計師編寫的bug、資源訪問許可權不足、資源找不到、HTTP版本不受支援等造成的異常,重試一萬次也不會成功,反而可能因為你不斷的重試造成伺服器崩潰。
所以說Webclient已經在原始碼中,將retryBackoff()標記為廢棄,建議使用retryWhen()代替它。retryWhen()可以指定針對某些異常進行重試,其他異常不做重試。
為了使用retryWhen(),需要引入下面的包
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-extra</artifactId>
</dependency>
3.1.人為製造超時異常-用於測試
為了能夠製造請求超時的異常場景,我們給連線超時設定為5毫秒,即:讓所有請求一定會超時。(沒有任何請求能在5毫秒內完成網路連線)
//認為設定請求超時時間為5毫秒,也就是請求一定會超時,一定會丟擲ConnectTimeoutException
TcpClient tcpClient = TcpClient
.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5); //5毫秒
WebClient webClient = WebClient.builder()
.baseUrl("http://jsonplaceholder.typicode.com")
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.build();
3.2.測試retryWhen
用Retry物件定義請求重試的條件,也就是retryWhen的when
Retry<?> retry = Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
.retryMax(3) // 重試3次
.backoff(Backoff.exponential(Duration.ofSeconds(5),Duration.ofSeconds(60),2,true));
Mono<String> mono = webClient
.get() //GET 請求
.uri("/posts/1") // 請求路徑,這裡的請求路徑是正確的
.retrieve()
.bodyToMono(String.class)
.retryWhen(retry); //滿足Retry條件進行重試
System.out.println("=====" + mono.block());
Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
表示只有針對ConnectTimeoutException連線超市異常才進行請求重試,這裡使用了java8的Predicate語法- Backoff.exponential表示按指數增長的時間間隔進行重試,可以自己指定指數重試因子,即指數的計數。這裡我們仍然使用2作為指數重試因子,第一次重試間隔5秒,第二次間隔10秒(5 x2),第三次間隔20秒(5x2x2)
- 為防止間隔時間指數級無限延長,Backoff.exponential最長的重試間隔不能超過60秒,第二個引數。
- retryWhen(retry) 滿足retry條件進行重試
3.3.retryWhen的其他方法
- onlyIf()表示捕獲到指定的某個異常,進行請求重試
- allBut()表示除了某個異常之外,其他的異常被捕獲則進行請求重試
- any() 表示針對所有異常,進行請求重試
- anyOf()表示指定某些異常型別,進行請求重試
backOff表示重試的時間間隔
- exponential()指數級增長的時間間隔
- fix()表示固定的時間間隔
歡迎關注我的部落格,裡面有很多精品合集
- 本文轉載註明出處(必須帶連線,不能只轉文字):字母哥部落格。
覺得對您有幫助的話,幫我點贊、分享!您的支援是我不竭的創作動力! 。另外,筆者最近一段時間輸出瞭如下的精品內容,期待您的關注。