![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbwxCdh1mcvZ2LcV2Zh1Wa9M3clN2byBXLzN3btg3PwJWZ35SM2YDOxYGOlBzMwkzMzQmZjNGM1MWN2kDOlVjZycDZ08CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.webp)
前言
沒有人能保證自己的系統不出錯,同樣,在調用第三方系統時,也不能保證能夠100%的成功。
往往會因為程式邏輯、網絡中斷、邊界值等各種各樣的問題導緻服務失敗。
在不同的業務領域對于服務的錯誤率有着不同的要求,一些金融領域的系統一般要求服務的錯誤率為0.01%。
那麼為了保證很低的錯誤率,則需要通過一些專門的機制來完成。而最常見的方式就是在出現錯誤時通過重試來解決。
場景
比如,在購買保險的場景中,使用者購買保險之前,需要根據使用者的個人資訊來查詢産品的報價。
在産品報價接口中,它調用了另一個服務查詢客戶資訊,假設這個客戶資訊需要從另一台獨立的微服務中去查詢。
如果客戶資訊查詢接口傳回500錯誤,那麼産品報價服務會怎麼給使用者傳回報價資訊呢?一種簡單的方法是直接傳回給使用者查詢失敗。
當然,更好的方法是通過重試客戶資訊查詢接口,來恢複報價服務查詢的功能。在我們依賴于另一個外部服務時,我們在某種程度上無法控制外部服務的穩定性,通過這種重試的自我恢複機制,可以有效地保證自己服務的穩定性,改善使用者體驗。
可能有些朋友要問了,如果客戶資訊查詢的接口一直失敗呢?難道要一直重試嗎?
當然不是。我們可以按照一定的重試機制,比如隻重試3次,如果3次都失敗,則重試結束。
那麼該如何實作這種重試呢?最簡單的方法就是在調用外部服務時,使用最原始的for循環。
但是這是一個很普遍、通用的場景,Spring架構的開發人員早就想到了。在Spring中已經封裝好了相應的API,可以讓我們簡單、靈活地來實作接口重試。
Spring重試機制spring-retry
最開始,spring的重試功能是和
spring-batch
放在一個子產品中的。從2.2.0版本開始,将
spring-retry
單獨成立了一個子產品。
如果我們要在Springboot項目中啟用這個功能,則需要在maven的
pom.xml
檔案中添加如下依賴:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
複制代碼
這個庫并沒有自動配置功能,它的
artifactId
也不是xxx-starter,我們需要在SpringBoot啟動類或者帶
@Configuration
注解的配置配置類中添加
@EnableRetry
注解,啟動重試功能。
如何使用
完成上面的依賴添加和
@EnableRetry
注解配置之後,接下來我們要對調用外部接口的服務增加重試功能,則很簡單。
注解聲明方式
使用
@Retryable
注解是最簡單快捷的方式。
@Service
public class RetryableCustomerClient {
@Autowired
private CustomerSrvClient customerSrvClient;
@Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(delay = 500L, maxDelay = 3000L, multiplier = 2, random = true))
public Optional<Customer> getCustomer(Long id) {
return customerSrvClient.getCustomer(id);
}
}
複制代碼
上述代碼中在
getCustomer
方法上的
@Retryable
注解表示該方法具備重試功能。其中的參數配置了重試的具體機制。
- value指定隻有該方法抛出RuntimeException時進行重試;
- maxAttempts 指定該方法最多重試3次
- backoff 指定每次重試的間隔在500ms-3000ms之間随機。
在添加
@Retryable
注解之後,Spring會在運作時建立一個代理對象,在這個代理中根據指定的重試參數執行重試邏輯,調用客戶資訊查詢的API。而這個代理對象是在系統啟動時才建立的,對産品報價服務透明,是以産品報價服務中不需要額外修改代碼。
使用注解方式基本可以滿足我們正常的一些重試機制的使用。
但是,如果你想再做一些更靈活、更自定義的重試政策,則使用注解則不太适合。
比如,在我們的這個案例中,我們想按照産品報價查詢時的不同産品,使用不同的重試政策。在這種情況下,我們可以使用寫代碼的方式來實作,與聲明式相對比也稱之為指令式。
指令式實作-支援動态重試政策
針對指令式實作,在spring-retry中專門提供了一個
RetryTemplate
類。這種方式需要在我們的業務邏輯中使用這個類。
在以下代碼示例中,實作了根據産品類型的不同,使用不同的重試政策。可以看出,使用RetryTemplate的确實要比注解靈活許多。
private Optional<Product> retrieveProduct(String productCode) {
// 比如交通意外險重試5次,其他保險産品重試2次
int maxAttempts = productCode.startsWith(TRAVEL_INSURANCE_PREFIX)? 5 : 2;
RetryTemplate retryTemplate = RetryTemplate.builder()
.maxAttempts(maxAttempts)
.retryOn(RuntimeException.class)
.exponentialBackoff(300L, 2, 5000L, true)
.build();
return retryTemplate.execute(arg -> productSrvClient.getProduct(productCode));
}
複制代碼
寫操作重試
在前面的案例中我們都是針對查詢接口進行重試,當然重試不僅在查詢時可用,在進行一些資料寫入、更新的操作中同樣可以使用。比如當我們在一個儲存訂單的操作中,可能會因為資料庫鎖逾時、連接配接逾時之類的一些原因導緻處理失敗,這種情況如果再進行一次重試基本都會成功。
但是這裡要特别注意,對于寫操作服務的重試,一定要保證服務的幂等性。 也就是說,當你進行多次執行時,結果應該可執行一次一樣。比如對于同一個訂單的儲存操作在重複執行多次後應該還是隻儲存一條記錄,而不是儲存多條。
如果訂單資料的實體主鍵是自增的,則必須使用其他的業務主鍵字段,保證其唯一性。
如何測試重試
對于我們的重試功能,雖然通過API我們能知道其功能,但是我們還是要針對不同的場景進行測試的。
但是對于重試場景的測試往往不太容易,在我團隊中的小夥伴很多次沒有對重試功能進行自測,其原因基本都是因為在測試環境是不友善模拟失敗的情況,比如我想讓它前兩次成功,第三次失敗之類。
對于這種情況的測試,我們需要依賴于一些mock測試工具,比如使用Mockito模拟測試。
關于Mockito的使用不是本期内容的重點,大家可以查詢相關文檔學習。
最後
使用重試機制往往會對提高系統的穩定性帶來很大的幫助,如果你隻是想用一些簡單的重試機制,那麼使用@Retryable注解即可,但是要更定制化,則可以使用RetryTemplate來靈活地完成。
當然了,在進行重試改造之前,一定要評估是否真的需要,以及對于寫操作的服務,是否保證了操作的幂等性。
以上就是本期的全部内容,希望對你有所幫助。