在本文中,我們將從快速介紹 Resilience4j 開始,然后深入探討其 Retry 模塊。我們將了解何時(shí)、如何使用它,以及它提供的功能。在此過程中,我們還將學(xué)習(xí)實(shí)現(xiàn)重試時(shí)的一些良好實(shí)踐。
代碼示例
本文在 GitHu 上附有工作代碼示例。
什么是 Resilience4j?
當(dāng)應(yīng)用程序通過網(wǎng)絡(luò)進(jìn)行通信時(shí),會(huì)有很多出錯(cuò)的情況。由于連接斷開、網(wǎng)絡(luò)故障、上游服務(wù)不可用等,操作可能會(huì)超時(shí)或失敗。應(yīng)用程序可能會(huì)相互過載、無響應(yīng)甚至崩潰。
Resilience4j 是一個(gè) Java 庫,可以幫助我們構(gòu)建彈性和容錯(cuò)的應(yīng)用程序。它提供了一個(gè)框架,可編寫代碼以防止和處理此類問題。
Resilience4j 為 Java 8 及更高版本編寫,適用于函數(shù)接口、lambda 表達(dá)式和方法引用等結(jié)構(gòu)。
Resilience4j 模塊
讓我們快速瀏覽一下這些模塊及其用途:
模塊 | 目的 |
---|---|
Retry | 自動(dòng)重試失敗的遠(yuǎn)程操作 |
RateLimiter | 限制我們?cè)谝欢〞r(shí)間內(nèi)調(diào)用遠(yuǎn)程操作的次數(shù) |
TimeLimiter | 調(diào)用遠(yuǎn)程操作時(shí)設(shè)置時(shí)間限制 |
Circuit Breaker | 當(dāng)遠(yuǎn)程操作持續(xù)失敗時(shí),快速失敗或執(zhí)行默認(rèn)操作 |
Bulkhead | 限制并發(fā)遠(yuǎn)程操作的數(shù)量 |
Cache | 存儲(chǔ)昂貴的遠(yuǎn)程操作的結(jié)果 |
使用范式
雖然每個(gè)模塊都有其抽象,但通常的使用范式如下:
- 創(chuàng)建一個(gè) Resilience4j 配置對(duì)象
- 為此類配置創(chuàng)建一個(gè) Registry 對(duì)象
- 從注冊(cè)表創(chuàng)建或獲取 Resilience4j 對(duì)象
- 將遠(yuǎn)程操作編碼為 lambda 表達(dá)式或函數(shù)式接口或通常的 Java 方法
- 使用提供的輔助方法之一圍繞第 4 步中的代碼創(chuàng)建裝飾器或包裝器
- 調(diào)用裝飾器方法來調(diào)用遠(yuǎn)程操作
步驟 1-5 通常在應(yīng)用程序啟動(dòng)時(shí)完成一次。讓我們看看重試模塊的這些步驟:
RetryConfig config = RetryConfig.ofDefaults(); // ----> 1RetryRegistry registry = RetryRegistry.of(config); // ----> 2Retry retry = registry.retry("flightSearchService", config); // ----> 3FlightSearchService searchService = new FlightSearchService();SearchRequest request = new SearchRequest("NYC", "LAX", "07/21/2020");Supplier> flightSearchSupplier = () -> searchService.searchFlights(request); // ----> 4Supplier> retryingFlightSearch = Retry.decorateSupplier(retry, flightSearchSupplier); // ----> 5System.out.println(retryingFlightSearch.get()); // ----> 6
什么時(shí)候使用重試?
遠(yuǎn)程操作可以是通過網(wǎng)絡(luò)發(fā)出的任何請(qǐng)求。通常,它是以下之一:
- 向 REST 端點(diǎn)發(fā)送 HTTP 請(qǐng)求
- 調(diào)用遠(yuǎn)程過程 (RPC) 或 Web 服務(wù)
- 從數(shù)據(jù)存儲(chǔ)(SQL/NoSQL 數(shù)據(jù)庫、對(duì)象存儲(chǔ)等)讀取和寫入數(shù)據(jù)
- 向消息代理(RabbitMQ/ActiveMQ/Kafka 等)發(fā)送和接收消息
當(dāng)遠(yuǎn)程操作失敗時(shí),我們有兩種選擇——立即向我們的客戶端返回錯(cuò)誤,或者重試操作。如果重試成功,這對(duì)客戶來說是件好事——他們甚至不必知道這是一個(gè)臨時(shí)問題。
選擇哪個(gè)選項(xiàng)取決于錯(cuò)誤類型(瞬時(shí)或永久)、操作(冪等或非冪等)、客戶端(人或應(yīng)用程序)和用例。
暫時(shí)性錯(cuò)誤是暫時(shí)的,通常,如果重試,操作很可能會(huì)成功。請(qǐng)求被上游服務(wù)限制、連接斷開或由于某些服務(wù)暫時(shí)不可用而超時(shí)就是例子。
來自 REST API 的硬件故障或 404(未找到)響應(yīng)是永久性錯(cuò)誤的示例,重試無濟(jì)于事。
如果我們想應(yīng)用重試,操作必須是冪等的。假設(shè)遠(yuǎn)程服務(wù)接收并處理了我們的請(qǐng)求,但在發(fā)送響應(yīng)時(shí)出現(xiàn)問題。在這種情況下,當(dāng)我們重試時(shí),我們不希望服務(wù)將請(qǐng)求視為新請(qǐng)求或返回意外錯(cuò)誤(想想銀行轉(zhuǎn)賬)。
重試會(huì)增加 API 的響應(yīng)時(shí)間。如果客戶端是另一個(gè)應(yīng)用程序,如 cron 作業(yè)或守護(hù)進(jìn)程,這可能不是問題。但是,如果是一個(gè)人,有時(shí)最好做出響應(yīng),快速失敗并提供反饋,而不是在我們不斷重試時(shí)讓這個(gè)人等待。
對(duì)于某些關(guān)鍵用例,可靠性可能比響應(yīng)時(shí)間更重要,即使客戶是個(gè)人,我們也可能需要實(shí)現(xiàn)重試。銀行轉(zhuǎn)賬或旅行社預(yù)訂航班和旅行酒店的轉(zhuǎn)賬就是很好的例子 - 用戶期望可靠性,而不是對(duì)此類用例的即時(shí)響應(yīng)。我們可以通過立即通知用戶我們已接受他們的請(qǐng)求并在完成后通知他們來做出響應(yīng)。
使用 Resilience4j 重試模塊
RetryRegistry
、RetryConfig
和 Retry
是 resilience4j-retry 中的主要抽象。RetryRegistry
是用于創(chuàng)建和管理 Retry
對(duì)象的工廠。RetryConfig
封裝了諸如應(yīng)該嘗試重試多少次、嘗試之間等待多長時(shí)間等配置。每個(gè) Retry
對(duì)象都與一個(gè) RetryConfig
相關(guān)聯(lián)。 Retr
y 提供了輔助方法來為包含遠(yuǎn)程調(diào)用的函數(shù)式接口或 lambda 表達(dá)式創(chuàng)建裝飾器。
讓我們看看如何使用 retry 模塊中可用的各種功能。假設(shè)我們正在為一家航空公司建立一個(gè)網(wǎng)站,以允許其客戶搜索和預(yù)訂航班。我們的服務(wù)與 FlightSearchService
類封裝的遠(yuǎn)程服務(wù)通信。
簡單重試
在簡單重試中,如果在遠(yuǎn)程調(diào)用期間拋出 RuntimeException
,則重試該操作。 我們可以配置嘗試次數(shù)、嘗試之間等待多長時(shí)間等:
RetryConfig config = RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.of(2, SECONDS)) .build();// Registry, Retry creation omittedFlightSearchService service = new FlightSearchService();SearchRequest request = new SearchRequest("NYC", "LAX", "07/31/2020");Supplier> flightSearchSupplier = () -> service.searchFlights(request);Supplier> retryingFlightSearch = Retry.decorateSupplier(retry, flightSearchSupplier);System.out.println(retryingFlightSearch.get());
我們創(chuàng)建了一個(gè) RetryConfig
,指定我們最多要重試 3 次,并在兩次嘗試之間等待 2 秒。如果我們改用 RetryConfig.ofDefaults()
方法,則將使用 3 次嘗試和 500 毫秒等待持續(xù)時(shí)間的默認(rèn)值。
我們將航班搜索調(diào)用表示為 lambda 表達(dá)式 - List<Flight>
的 Supplier
。Retry.decorateSupplier()
方法使用重試功能裝飾此 Supplier
。最后,我們?cè)谘b飾過的 Supplier
上調(diào)用 get()
方法來進(jìn)行遠(yuǎn)程調(diào)用。
如果我們想創(chuàng)建一個(gè)裝飾器并在代碼庫的不同位置重用它,我們將使用 decorateSupplier()
。如果我們想創(chuàng)建它并立即執(zhí)行它,我們可以使用 executeSupplier()
實(shí)例方法代替:
List flights = retry.executeSupplier( () -> service.searchFlights(request));這是顯示第一個(gè)請(qǐng)求失敗然后第二次嘗試成功的示例輸出:Searching for flights; current time = 20:51:34 975Operation failedSearching for flights; current time = 20:51:36 985Flight search successful[Flight{flightNumber=XY 765, flightDate=07/31/2020, from=NYC, to=LAX}, ...]
在已檢異常上重試
現(xiàn)在,假設(shè)我們要重試已檢查和未檢查的異常。假設(shè)我們正在調(diào)用FlightSearchService.searchFlightsThrowingException()
,它可以拋出一個(gè)已檢查的 Exception
。由于 Supplier
不能拋出已檢查的異常,我們會(huì)在這一行得到編譯器錯(cuò)誤:
Supplier> flightSearchSupplier = () -> service.searchFlightsThrowingException(request);
我們可能會(huì)嘗試在 lambda 表達(dá)式中處理 Exception 并返回 Collections.emptyList()
,但這看起來不太好。更重要的是,由于我們自己捕獲 Exception
,重試不再起作用:
ExceptionSupplier> flightSearchSupplier = () -> { try { return service.searchFlightsThrowingException(request); } catch (Exception e) { // dont do this, this breaks the retry! } return Collections.emptyList(); };
那么當(dāng)我們想要重試遠(yuǎn)程調(diào)用可能拋出的所有異常時(shí),我們應(yīng)該怎么做呢?我們可以使用Retry.decorateCheckedSupplier()
(或 executeCheckedSupplier()
實(shí)例方法)代替 Retry.decorateSupplier()
:
CheckedFunction0> retryingFlightSearch = Retry.decorateCheckedSupplier(retry, () -> service.searchFlightsThrowingException(request));try { System.out.println(retryingFlightSearch.apply());} catch (...) { // handle exception that can occur after retries are exhausted}
Retry.decorateCheckedSupplier()
返回一個(gè) CheckedFunction0
,它表示一個(gè)沒有參數(shù)的函數(shù)。請(qǐng)注意對(duì) CheckedFunction0
對(duì)象的 apply()
調(diào)用以調(diào)用遠(yuǎn)程操作。
如果我們不想使用 Suppliers
,Retry
提供了更多的輔助裝飾器方法,如 decorateFunction()
、decorateCheckedFunction()
、decorateRunnable()
、decorateCallable()
等,以與其他語言結(jié)構(gòu)一起使用。decorate*
和 decorateChecked*
版本之間的區(qū)別在于,decorate*
版本在 RuntimeExceptions
上重試,而 decorateChecked*
版本在 Exception
上重試。
有條件重試
上面的簡單重試示例展示了如何在調(diào)用遠(yuǎn)程服務(wù)時(shí)遇到 RuntimeException
或已檢查 Exception
時(shí)重試。在實(shí)際應(yīng)用中,我們可能不想對(duì)所有異常都重試。 例如,如果我們得到一個(gè)AuthenticationFailedException
重試相同的請(qǐng)求將無濟(jì)于事。當(dāng)我們進(jìn)行 HTTP 調(diào)用時(shí),我們可能想要檢查 HTTP 響應(yīng)狀態(tài)代碼或在響應(yīng)中查找特定的應(yīng)用程序錯(cuò)誤代碼來決定是否應(yīng)該重試。讓我們看看如何實(shí)現(xiàn)這種有條件的重試。
Predicate-based條件重試
假設(shè)航空公司的航班服務(wù)定期初始化其數(shù)據(jù)庫中的航班數(shù)據(jù)。對(duì)于給定日期的飛行數(shù)據(jù),此內(nèi)部操作需要幾秒鐘時(shí)間。 如果我們?cè)诔跏蓟^程中調(diào)用當(dāng)天的航班搜索,該服務(wù)將返回一個(gè)特定的錯(cuò)誤代碼 FS-167。航班搜索文檔說這是一個(gè)臨時(shí)錯(cuò)誤,可以在幾秒鐘后重試該操作。
讓我們看看如何創(chuàng)建 RetryConfig
:
RetryConfig config = RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.of(3, SECONDS)) .retryOnResult(searchResponse -> searchResponse .getErrorCode() .equals("FS-167")) .build();
我們使用 retryOnResult()
方法并傳遞執(zhí)行此檢查的 Predicate
。這個(gè) Predicate
中的邏輯可以像我們想要的那樣復(fù)雜——它可以是對(duì)一組錯(cuò)誤代碼的檢查,也可以是一些自定義邏輯來決定是否應(yīng)該重試搜索。
Exception-based條件重試
假設(shè)我們有一個(gè)通用異常FlightServiceBaseException
,當(dāng)在與航空公司的航班服務(wù)交互期間發(fā)生任何意外時(shí)會(huì)拋出該異常。作為一般策略,我們希望在拋出此異常時(shí)重試。但是我們不想重試 SeatsUnavailableException
的一個(gè)子類 - 如果航班上沒有可用座位,重試將無濟(jì)于事。我們可以通過像這樣創(chuàng)建 RetryConfig
來做到這一點(diǎn):
RetryConfig config = RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.of(3, SECONDS)) .retryExceptions(FlightServiceBaseException.class) .ignoreExceptions(SeatsUnavailableException.class) .build();
在 retryExceptions()
中,我們指定了一個(gè)異常列表。ignoreExceptions()
將重試與此列表中的異常匹配或繼承的任何異常。我們把我們想忽略而不是重試的那些放入ignoreExceptions()
。如果代碼在運(yùn)行時(shí)拋出一些其他異常,比如 IOException
,它也不會(huì)被重試。
假設(shè)即使對(duì)于給定的異常,我們也不希望在所有情況下都重試。也許我們只想在異常具有特定錯(cuò)誤代碼或異常消息中的特定文本時(shí)重試。在這種情況下,我們可以使用 retryOnException
方法:
Predicate rateLimitPredicate = rle -> (rle instanceof RateLimitExceededException) && "RL-101".equals(((RateLimitExceededException) rle).getErrorCode());RetryConfig config = RetryConfig.custom() .maxAttempts(3) .waitDuration(Duration.of(1, SECONDS)) .retryOnException(rateLimitPredicate) build();
與 predicate-based (基于謂詞)的條件重試一樣,謂詞內(nèi)的檢查可以根據(jù)需要復(fù)雜化。
退避策略
到目前為止,我們的示例有固定的重試等待時(shí)間。通常我們希望在每次嘗試后增加等待時(shí)間——這是為了讓遠(yuǎn)程服務(wù)有足夠的時(shí)間在當(dāng)前過載的情況下進(jìn)行恢復(fù)。我們可以使用 IntervalFunction
來做到這一點(diǎn)。
IntervalFunction
是一個(gè)函數(shù)式接口——它是一個(gè)以嘗試次數(shù)為參數(shù)并以毫秒為單位返回等待時(shí)間的 Function
。
隨機(jī)間隔
這里我們指定嘗試之間的隨機(jī)等待時(shí)間:
RetryConfig config = RetryConfig.custom().maxAttempts(4).intervalFunction(IntervalFunction.ofRandomized(2000)).build();
IntervalFunction.ofRandomized()
有一個(gè)關(guān)聯(lián)的 randomizationFactor
。我們可以將其設(shè)置為 ofRandomized()
的第二個(gè)參數(shù)。如果未設(shè)置,則采用默認(rèn)值 0.5。這個(gè) randomizationFactor
決定了隨機(jī)值的分布范圍。因此,對(duì)于上面的默認(rèn)值 0.5,生成的等待時(shí)間將介于 1000 毫秒(2000 - 2000 0.5)和 3000 毫秒(2000 + 2000 0.5)之間。
這種行為的示例輸出如下:
Searching for flights; current time = 20:27:08 729Operation failedSearching for flights; current time = 20:27:10 643Operation failedSearching for flights; current time = 20:27:13 204Operation failedSearching for flights; current time = 20:27:15 236Flight search successful[Flight{flightNumber=XY 765, flightDate=07/31/2020, from=NYC, to=LAX},...]
指數(shù)間隔
對(duì)于指數(shù)退避,我們指定兩個(gè)值 - 初始等待時(shí)間和乘數(shù)。在這種方法中,由于乘數(shù),等待時(shí)間在嘗試之間呈指數(shù)增長。例如,如果我們指定初始等待時(shí)間為 1 秒,乘數(shù)為 2,則重試將在 1 秒、2 秒、4 秒、8 秒、16 秒等之后進(jìn)行。當(dāng)客戶端是后臺(tái)作業(yè)或守護(hù)進(jìn)程時(shí),此方法是推薦的方法。
以下是我們?nèi)绾螢橹笖?shù)退避創(chuàng)建 RetryConfig
:
RetryConfig config = RetryConfig.custom().maxAttempts(6).intervalFunction(IntervalFunction.ofExponentialBackoff(1000, 2)).build();
這種行為的示例輸出如下:
Searching for flights; current time = 20:37:02 684Operation failedSearching for flights; current time = 20:37:03 727Operation failedSearching for flights; current time = 20:37:05 731Operation failedSearching for flights; current time = 20:37:09 731Operation failedSearching for flights; current time = 20:37:17 731
IntervalFunction
還提供了一個(gè) exponentialRandomBackoff()
方法,它結(jié)合了上述兩種方法。我們還可以提供 IntervalFunction 的自定義實(shí)現(xiàn)。
重試異步操作
直到現(xiàn)在我們看到的例子都是同步調(diào)用。讓我們看看如何重試異步操作。假設(shè)我們像這樣異步搜索航班:
CompletableFuture.supplyAsync(() -> service.searchFlights(request)) .thenAccept(System.out::println);
searchFlight(
) 調(diào)用發(fā)生在不同的線程上,當(dāng)它返回時(shí),返回的 List<Flight>
被傳遞給 thenAccept()
,它只是打印它。
我們可以使用 Retry
對(duì)象上的 executeCompletionStage()
方法對(duì)上述異步操作進(jìn)行重試。 此方法采用兩個(gè)參數(shù) - 一個(gè) ScheduledExecutorService
將在其上安排重試,以及一個(gè) Supplier<CompletionStage>
將被裝飾。它裝飾并執(zhí)行 CompletionStage
,然后返回一個(gè) CompletionStage
,我們可以像以前一樣調(diào)用 thenAccept
:
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();Supplier>> completionStageSupplier = () -> CompletableFuture.supplyAsync(() -> service.searchFlights(request));retry.executeCompletionStage(scheduler, completionStageSupplier).thenAccept(System.out::println);
在實(shí)際應(yīng)用程序中,我們將使用共享線程池 (Executors.newScheduledThreadPool()
) 來調(diào)度重試,而不是此處顯示的單線程調(diào)度執(zhí)行器。
重試事件
在所有這些例子中,裝飾器都是一個(gè)黑盒子——我們不知道什么時(shí)候嘗試失敗了,框架代碼正在嘗試重試。假設(shè)對(duì)于給定的請(qǐng)求,我們想要記錄一些詳細(xì)信息,例如嘗試計(jì)數(shù)或下一次嘗試之前的等待時(shí)間。 我們可以使用在不同執(zhí)行點(diǎn)發(fā)布的重試事件來做到這一點(diǎn)。Retry 有一個(gè) EventPublisher
,它具有 onRetry()
、onSuccess()
等方法。
我們可以通過實(shí)現(xiàn)這些監(jiān)聽器方法來收集和記錄詳細(xì)信息:
Retry.EventPublisher publisher = retry.getEventPublisher();publisher.onRetry(event -> System.out.println(event.toString()));publisher.onSuccess(event -> System.out.println(event.toString()));
類似地,RetryRegistry
也有一個(gè) EventPublisher
,它在 Retry
對(duì)象被添加或從注冊(cè)表中刪除時(shí)發(fā)布事件。
重試指標(biāo)
Retry
維護(hù)計(jì)數(shù)器以跟蹤操作的次數(shù)
- 第一次嘗試成功
- 重試后成功
- 沒有重試就失敗了
- 重試后仍失敗
每次執(zhí)行裝飾器時(shí),它都會(huì)更新這些計(jì)數(shù)器。
為什么要捕獲指標(biāo)?
捕獲并定期分析指標(biāo)可以讓我們深入了解上游服務(wù)的行為。它還可以幫助識(shí)別瓶頸和其他潛在問題。
例如,如果我們發(fā)現(xiàn)某個(gè)操作通常在第一次嘗試時(shí)失敗,我們可以調(diào)查其原因。如果我們發(fā)現(xiàn)我們的請(qǐng)求在建立連接時(shí)受到限制或超時(shí),則可能表明遠(yuǎn)程服務(wù)需要額外的資源或容量。
如何捕獲指標(biāo)?
Resilience4j 使用 Micrometer 發(fā)布指標(biāo)。Micrometer 為監(jiān)控系統(tǒng)(如 Prometheus、Azure Monitor、New Relic 等)提供了儀表客戶端的外觀。因此我們可以將指標(biāo)發(fā)布到這些系統(tǒng)中的任何一個(gè)或在它們之間切換,而無需更改我們的代碼。
首先,我們像往常一樣創(chuàng)建 RetryConfig
和 RetryRegistry
和 Retry
。然后,我們創(chuàng)建一個(gè) MeterRegistry
并將 etryRegistry
綁定到它:
MeterRegistry meterRegistry = new SimpleMeterRegistry();TaggedRetryMetrics.ofRetryRegistry(retryRegistry).bindTo(meterRegistry);
運(yùn)行幾次可重試操作后,我們顯示捕獲的指標(biāo):
Consumer meterConsumer = meter -> { String desc = meter.getId().getDescription(); String metricName = meter.getId().getTag("kind"); Double metricValue = StreamSupport.stream(meter.measure().spliterator(), false) .filter(m -> m.getStatistic().name().equals("COUNT")) .findFirst() .map(m -> m.getValue()) .orElse(0.0); System.out.println(desc + " - " + metricName + ": " + metricValue);};meterRegistry.forEachMeter(meterConsumer);
一些示例輸出如下:
The number of successful calls without a retry attempt - successful_without_retry: 4.0The number of failed calls without a retry attempt - failed_without_retry: 0.0The number of failed calls after a retry attempt - failed_with_retry: 0.0The number of successful calls after a retry attempt - successful_with_retry: 6.0
當(dāng)然,在實(shí)際應(yīng)用中,我們會(huì)將數(shù)據(jù)導(dǎo)出到監(jiān)控系統(tǒng)并在儀表板上查看。
重試時(shí)的注意事項(xiàng)和良好實(shí)踐
服務(wù)通常提供具有內(nèi)置重試機(jī)制的客戶端庫或 SDK。對(duì)于云服務(wù)尤其如此。 例如,Azure CosmosDB 和 Azure 服務(wù)總線為客戶端庫提供內(nèi)置重試工具。 它們?cè)试S應(yīng)用程序設(shè)置重試策略來控制重試行為。
在這種情況下,最好使用內(nèi)置的重試而不是我們自己的編碼。如果我們確實(shí)需要自己編寫,我們應(yīng)該禁用內(nèi)置的默認(rèn)重試策略 - 否則,它可能導(dǎo)致嵌套重試,其中應(yīng)用程序的每次嘗試都會(huì)導(dǎo)致客戶端庫的多次嘗試。
一些云服務(wù)記錄瞬時(shí)錯(cuò)誤代碼。例如,Azure SQL 提供了它期望數(shù)據(jù)庫客戶端重試的錯(cuò)誤代碼列表。在決定為特定操作添加重試之前,最好檢查一下服務(wù)提供商是否有這樣的列表。
另一個(gè)好的做法是將我們?cè)?RetryConfig 中使用的值(例如最大嘗試次數(shù)、等待時(shí)間和可重試錯(cuò)誤代碼和異常)作為我們服務(wù)之外的配置進(jìn)行維護(hù)。如果我們發(fā)現(xiàn)新的暫時(shí)性錯(cuò)誤或者我們需要調(diào)整嘗試之間的間隔,我們可以在不構(gòu)建和重新部署服務(wù)的情況下進(jìn)行更改。
通常在重試時(shí),框架代碼中的某處可能會(huì)發(fā)生 Thread.sleep()。對(duì)于在重試之間有等待時(shí)間的同步重試就是這種情況。如果我們的代碼在 Web 應(yīng)用程序的上下文中運(yùn)行,則 Thread 很可能是 Web 服務(wù)器的請(qǐng)求處理線程。因此,如果我們進(jìn)行過多的重試,則會(huì)降低應(yīng)用程序的吞吐量。
結(jié)論
在本文中,我們了解了 Resilience4j 是什么,以及如何使用它的重試模塊使我們的應(yīng)用程序可以在應(yīng)對(duì)臨時(shí)錯(cuò)誤具備彈性。我們研究了配置重試的不同方法,以及在不同方法之間做出決定的一些示例。我們學(xué)習(xí)了一些在實(shí)施重試時(shí)要遵循的良好實(shí)踐,以及收集和分析重試指標(biāo)的重要性。
您可以使用 GitHub 上的代碼嘗試一個(gè)完整的應(yīng)用程序來演示這些想法。