分享概要
一、背景
二、方案
三、效果
四、可選項
五、註意事項
六、總結
一、背景
在分布式場景下,為了保障系統的可用性和數據的最終一致性,采用基於訊息佇列(MQ)的重試機制是一種常見的解決方案。虛擬碼如下:
/**
* 需要保證最終一致性的函式
*/
publicvoiddoSomething(Object args) {
try {
// 執行事務的操作
executeTransaction();
// 送出事務
commitTransaction();
} catch (Exception e) {
// 回滾事務
rollbackTransaction();
// 記錄日誌
log.error(e);
// 序列化參數
byte[] body = serialize(args);
// 構建訊息, 指定Topic、Body
Message msg = new Message("doSomethingTopic", body);
// 發送失敗重試訊息
mq.send(msg);
}
}
/**
* 消費者,用於失敗重試處理
*/
@Consumer(topic = "doSomethingTopic")
publicvoidconsume(Message msg) {
// 反序列化
Object args = msg.deserialize();
// 重試
doSomething(args);
}
在上述範例中,我們需要編寫一系列與業務無關的程式碼來實作業務邏輯的重試機制。為了減輕開發人員的負擔並讓其專註於核心業務,我們可以對這些無關程式碼進行抽象和最佳化,以提高開發效率和程式碼品質。
二、方案
透過如下步驟,我們對重試邏輯進行了封裝,開發人員只需要在需要保證最終一致性的函式上標註一個重試註解,便擁有基於MQ的分布式重試能力。
1. 使用註解與AOP: 透過使用註解與面向切面編程(AOP)的技術,將重試邏輯模組與業務程式碼解耦。開發人員可以在需要保證最終一致性的業務方法上添加註解,透過AOP將重試邏輯套用到目標方法中,從而自動觸發重試機制。
2. 提供配置化選項: 為重試邏輯提供可配置化的選項,例如設定最大重試次數、重試間隔時間等。這樣,開發人員可以根據具體業務需求進行調整,而無需修改程式碼。
3. 例外處理和日誌記錄: 在重試邏輯中合理地處理異常,並在必要時記錄相關日誌。這樣可以幫助開發人員及時發現問題並進行排查。
4. 提供視覺化監控工具: 開發一個視覺化的監控工具,用於即時跟蹤重試操作和相關指標。這樣可以幫助開發人員更好地理解重試的執行情況,並進行故障排查和效能最佳化。
三、效果
我們引入了@MQRetry註解用於標記業務邏輯函式,一旦該函式發生異常,該註解會將服務名、類的完整名稱、方法名稱以及實際參數列發送到訊息佇列(MQ)中。同時系統會註冊一個MQ消費者來消費這些訊息,並進行重試處理。
舉個例子,假設我們有一個名為doSomething的函式,它包含了需要保證最終一致性執行的業務邏輯。僅需在該函式上添加@MQRetry註解,當函式出現異常時,框架會自動發送一條MQ重試訊息。這條訊息可以被當前服務的任意一台伺服器消費,並重新執行doSomething函式。
@Service
class Service {
@MQRetry
publicvoid doSomething(String params1, String params2, List<String> params3) {
//throw new RuntimeException(); 拋異常將重試
//RetryContext.markRetryLater(); 標記為需要下次重試
//int retryCount = RetryContext.getRetryCount(); 獲取重試次數
}
}
@Controller
class Controller {
@Autowired
private Service service;
service.doSomething("1", "2", Arrays.asList("3", "4"));
}
四、可選項
除此之外,我們還為開發人員提供了一些可選項,提供一些可配置的能力。
/**
* 基於MQ的分布式重試元件
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interface MQRetry {
/**
* 最大重試次數,預設與上限為16次
*/
int maxAttempts() default16;
/**
* 忽略的異常類列表,預設所有異常都重試
*/
class<? extends Throwable>[] exclude() default {};
/**
* 需要重試的異常類列表,預設所有異常都重試
*/
class<? extends Throwable>[] include() default {};
/**
* 出現異常時的處理常式, 格式: Bean名.方法名. 如: smsService.onError
* 也可以只設定函式名, 不設定Bean名將執行本類的函式. 如: onError
* 要求函式參數必須與重試函式的參數完全一致
*/
String errorHandler() default"";
/**
* true: 第一次呼叫時, 同步執行@MQRetry函式, 如果失敗再使用MQ
* false: 呼叫@MQRetry函式時, 只會發送MQ
*/
boolean firstSyncCall() defaulttrue;
/**
* 消費執行緒數,預設為20個
*/
int consumeThread() default20;
}
五、註意事項
適用於異步場景,重試函式不要設定返回值,函式的返回值將不會有任何的實際意義。
At lease Once保證,重試函式需要 保證冪等 。
使用了AOP代理實作,因此,@Transactional的註意事項同樣適用於@MQRetry,如 this呼叫、private函式、final函式會導致重試失效 。
如果重試函式需要增加參數,請在函式參數最後位置添加。 歷史訊息消費時對應參數將填充為null。
六、總結
在電腦領域中,重試機制的重要性不言而喻。它通常分為兩種模式:客戶端模式和伺服端模式。客戶端模式簡單易用,但可靠性較低;而伺服端模式雖然相對復雜,但能夠提供更高的可靠性。
無論是客戶端模式還是伺服端模式,重試機制都是保障系統正常執行的重要一環。選擇適合您業務需求的模式,並透過合理的配置項進行最佳化,將為您的系統帶來更好的表現和使用者體驗。
關於作者
苑沖, 轉轉架構部儲存服務負責人,負責MQ、監控系統、KV儲存、時序資料庫、Redis、KMS秘鑰管理等基礎元件。喜歡深入思考問題,對探索新領域和解決問題充滿熱情。
作者丨苑沖
來源丨公眾號:轉轉技術(ID:zhuanzhuantech)
dbaplus社群歡迎廣大技術人員投稿,投稿信箱: [email protected]