背景
在很多業務場景中,我們都會使用消息隊列。而消息隊列的ACK機制,讓我們在處理消息時更有保障。
比如,當consumer從隊列中獲取到消息,進行消費處理時,服務宕機了。
- ACK機制,就保障這些在預設的超時時間內,未被正常消費的消息,會重新被安排消費。
- 如果服務還在正常處理,但是超過了超時時間,會帶來消息重複消費問題。
本文的目標,通過增加消息的監聽器,來解決由於消費超時,導致的重複消費問題。
另外,通過組件封裝,與大家分享可複用代碼的編寫思路。進一步理解面向對象設計中的開閉原則。實現的代碼片段較爲簡單,可以隨處複製到項目中使用。但是對於邏輯中的超時時間、續期間隔等配置參數,如果複製到新的業務邏輯中,加之代碼修改,容易產生BUG。而通過封裝,對外提供擴展策略,關閉代碼修改,使得複用組件時,可以專注業務邏輯開發。
實現流程
在消息隊列中,消息的生命週期包含:
- 發送,由生產者將消息寫入到隊列
- 接受,由消費者將消息從隊列中取走
- 回退,消費異常,重新進行Step.2
- 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 | 否 | 默認:丟棄策略。可自定義擴展 |
遇到問題
- SQS中消息不可見時間最大爲12小時,如果過長會被拒絕
- 消息隊列中的消息是非期望的
附錄
- Gitee項目倉庫
- 不錯的代碼繪圖工具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