解決消息隊列中因消費超時導致的重複消費

背景

在很多業務場景中,我們都會使用消息隊列。而消息隊列的ACK機制,讓我們在處理消息時更有保障。

比如,當consumer從隊列中獲取到消息,進行消費處理時,服務宕機了。

  • ACK機制,就保障這些在預設的超時時間內,未被正常消費的消息,會重新被安排消費。
  • 如果服務還在正常處理,但是超過了超時時間,會帶來消息重複消費問題。

本文的目標,通過增加消息的監聽器,來解決由於消費超時,導致的重複消費問題

另外,通過組件封裝,與大家分享可複用代碼的編寫思路。進一步理解面向對象設計中的開閉原則。實現的代碼片段較爲簡單,可以隨處複製到項目中使用。但是對於邏輯中的超時時間、續期間隔等配置參數,如果複製到新的業務邏輯中,加之代碼修改,容易產生BUG。而通過封裝,對外提供擴展策略,關閉代碼修改,使得複用組件時,可以專注業務邏輯開發。

實現流程

在消息隊列中,消息的生命週期包含:

  1. 發送,由生產者將消息寫入到隊列
  2. 接受,由消費者將消息從隊列中取走
  3. 回退,消費異常,重新進行Step.2
  4. ACK,消費完成,通知消息隊列清理消息

解決方案:在Step.2和Step.4的操作過程中,將消息狀態提供給消息監聽器Listener,通過線程遍歷,來完成不可見消息時間的自動更新

代碼實現

本文的技術實現,是基於aws sqs服務

Listener邏輯

private void listen() {
        new Thread(() -> {
            log.info("start message visibility listener");
            while (true) {
                for (Map.Entry<String, ObjTime> entry : objTimeMap.entrySet()) {
                    ObjTime time = entry.getValue();

                    if ((System.currentTimeMillis()
                            - time.getTime()) / 1000
                            + updateVisibilityInterval > timeout) {
                        // 重新放入obj對象信息
                        time.setTime(System.currentTimeMillis());
                        amazonSQS.changeMessageVisibility(queueUrl,
                                time.getObj().getSqsReceiptHandle(),
                                timeout);
                        messageObjectConverter.log("visibility", time.getObj());
                    }
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }

使用手冊

Maven依賴

說明:依賴並未發佈到公共倉庫中,可參考附錄中項目倉庫。

<dependency>
    <groupId>com.yanmushi</groupId>
    <artifactId>aws-sqs-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

屬性配置

核心服務:AwsSqsService

屬性 是否必須 說明
AmazonSQS 消息隊列客戶端
QueueName 消息隊列名稱
MessageObjectConverter 消息與實體對象之間轉換器,需要自定義實現
Timeout 默認:60s。消息不可見時間。
UpdateVisibilityInterval 默認:5s。到期5s前,延長消息不可見時間
IllegalMessageHandler 默認:丟棄策略。可自定義擴展

遇到問題

  1. SQS中消息不可見時間最大爲12小時,如果過長會被拒絕
  2. 消息隊列中的消息是非期望的

附錄

  1. Gitee項目倉庫
  2. 不錯的代碼繪圖工具PlantUML
    消息隊列的工作模式
@startuml
消息生產者 -> 隊列SQS : 1. 發送
隊列SQS -> 消息消費者  : 2. 接收
隊列SQS <-- 消息消費者 : 3. 回退(消費異常)
隊列SQS <- 消息消費者 : 4. ACK
@enduml

引入Listener處理邏輯

@startuml
隊列SQS -> 消息消費者  : 2. 接收
消息消費者 -> 消息Listener : 記錄消息及時間
消息Listener -> 消息Listener : 遍歷消息,更新將到期消息
隊列SQS <-- 消息消費者 : 3. 回退(消費異常)
隊列SQS <- 消息消費者 : 4. ACK
消息消費者 -> 消息Listener : 移除消息
@enduml
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章