轉轉基於MQ的分散式重試框架設計方案

架構師修行手冊發表於2024-02-01

來源:轉轉技術

  • 1 背景

  • 2 方案

  • 3 效果

  • 4 可選項

  • 5 注意事項

  • 6 總結

1 背景

在分散式場景下,為了保障系統的可用性和資料的最終一致性,採用基於訊息佇列(MQ)的重試機制是一種常見的解決方案。虛擬碼如下:

/**
 * 需要保證最終一致性的函式
 */

public void doSomething(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")
public void consume(Message msg) {
    // 反序列化
    Object args = msg.deserialize();
    // 重試
    doSomething(args);
}

在上述示例中,我們需要編寫一系列與業務無關的程式碼來實現業務邏輯的重試機制。為了減輕開發人員的負擔並讓其專注於核心業務,我們可以對這些無關程式碼進行抽象和最佳化,以提高開發效率和程式碼質量。

2 方案

透過如下步驟,我們對重試邏輯進行了封裝,開發人員只需要在需要保證最終一致性的函式上標註一個重試註解,便擁有基於MQ的分散式重試能力。

1. 使用註解與AOP: 透過使用註解與面向切面程式設計(AOP)的技術,將重試邏輯模組與業務程式碼解耦。開發人員可以在需要保證最終一致性的業務方法上新增註解,透過AOP將重試邏輯應用到目標方法中,從而自動觸發重試機制。

2. 提供配置化選項:為重試邏輯提供可配置化的選項,例如設定最大重試次數、重試間隔時間等。這樣,開發人員可以根據具體業務需求進行調整,而無需修改程式碼。

3. 異常處理和日誌記錄:在重試邏輯中合理地處理異常,並在必要時記錄相關日誌。這樣可以幫助開發人員及時發現問題並進行排查。

4. 提供視覺化監控工具:開發一個視覺化的監控工具,用於實時跟蹤重試操作和相關指標。這樣可以幫助開發人員更好地理解重試的執行情況,並進行故障排查和效能最佳化。

轉轉基於MQ的分散式重試框架設計方案

3 效果

我們引入了@MQRetry註解用於標記業務邏輯函式,一旦該函式發生異常,該註解會將服務名、類的完整名稱、方法名稱以及實際引數列表傳送到訊息佇列(MQ)中。同時系統會註冊一個MQ消費者來消費這些訊息,並進行重試處理。

舉個例子,假設我們有一個名為doSomething的函式,它包含了需要保證最終一致性執行的業務邏輯。僅需在該函式上新增@MQRetry註解,當函式出現異常時,框架會自動傳送一條MQ重試訊息。這條訊息可以被當前服務的任意一臺伺服器消費,並重新執行doSomething函式。

@Service
class Service {
 
    @MQRetry
    public void 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"));
}

4 可選項

除此之外,我們還為開發人員提供了一些可選項,提供一些可配置的能力。

/**
 * 基於MQ的分散式重試元件
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MQRetry {
    /**
     * 最大重試次數,預設與上限為16次
     */

    int maxAttempts() default 16;
    
    /**
     * 忽略的異常類列表,預設所有異常都重試
     */

    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() default true;
    
    /**
     * 消費執行緒數,預設為20個
     */

    int consumeThread() default 20;
    
}

5 注意事項

  1. 適用於非同步場景,重試函式不要設定返回值,函式的返回值將不會有任何的實際意義。

  2. At lease Once保證,重試函式需要保證冪等。

  3. 使用了AOP代理實現,因此,@Transactional的注意事項同樣適用於@MQRetry,如this呼叫、private函式、final函式會導致重試失效。

  4. 如果重試函式需要增加引數,請在函式引數最後位置新增。歷史訊息消費時對應引數將填充為null。

6 總結

在計算機領域中,重試機制的重要性不言而喻。它通常分為兩種模式:客戶端模式服務端模式。客戶端模式簡單易用,但可靠性較低;而服務端模式雖然相對複雜,但能夠提供更高的可靠性。

無論是客戶端模式還是服務端模式,重試機制都是保障系統正常執行的重要一環。選擇適合您業務需求的模式,並透過合理的配置項進行最佳化,將為您的系統帶來更好的表現和使用者體驗。

轉轉基於MQ的分散式重試框架設計方案

關於作者

苑衝,轉轉架構部儲存服務負責人,負責MQ、監控系統、KV儲存、時序資料庫、Redis、KMS秘鑰管理等基礎元件。喜歡深入思考問題,對探索新領域和解決問題充滿熱情。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70027824/viewspace-3006006/,如需轉載,請註明出處,否則將追究法律責任。

相關文章