實際業務處理 Kafka 訊息丟失、重複消費和順序消費的問題

小馮Coding發表於2022-04-05

關於 Kafka 訊息丟失、重複消費和順序消費的問題

訊息丟失,訊息重複消費,訊息順序消費等問題是我們使用 MQ 時不得不考慮的一個問題,下面我結合實際的業務來和你分享一下解決方案。

訊息丟失問題

比如我們使用 Kakfa 時,以下場景都會發生訊息丟失:

  • producer -> broker (生產者生產訊息)
  • broker -> broker (叢集環境,broker 同步給其他 broker)
  • broker -> consumer (消費者消費訊息)

解決方案也很簡單,設定 acks(訊息確認機制)retries(重試機制)factor(設定 partition 數量)...

一般來說,最常見的訊息丟失場景就是:consumer 消費訊息

要保證 consumer 消費訊息時不丟失訊息,必須使用手動提交 ack

我們業務是這樣實現的:

  1. Kafka 拉取訊息(一次批量拉取 100條)
  2. 為每條訊息分配一個 msgId(遞增)
  3. msgId 存入記憶體佇列(sortSet)
  4. 使用 Map 儲存 msgIdmsg (包含 offset)的對映關係
  5. 當業務處理完訊息後,獲取當前訊息的 msgId,然後從 sortSet 中刪除該 msgId(表示該訊息已經處理過了)
  6. ack 時,如果當前 msgId <= sortSet(msgId 在 sortSet 中是從小到大排列) ,就提交當前 offset
  7. 就算 consumer 在處理訊息時掛了,下次重啟時就會從 sortSet 隊首的訊息開始拉取,實現至少處理一次語義。
  8. 步驟 7 存在一個問題:當訊息處理完後,還沒從 sortSet 中刪除該 msgId,系統就掛了,當系統重啟時,又會重新處理一次剛剛已處理過的訊息,這就引出訊息重複消費的問題了。

訊息重複消費

要解決訊息重複消費,也就是要實現冪等(冪等就是:多次請求,但結果保持不變,舉一個例子你就明白了:在 http 中,你傳送同一個 get 請求,無論傳送多少次,返回結果都是一樣的

回到我們的業務場景上,我以處理訂單訊息為例:

  • 冪等Key 由我們的訂單Id + 訂單狀態組成(一筆訂單的狀態只會處理一次)

  • 在處理之前,我們首先會去 Redis 查詢是否存在這個 Key

    如果存在,說明我們已經處理過了,直接丟掉;

    ​ 如果不存在,說明沒處理過,繼續往下處理;

  • 最終的邏輯是:將處理過的資料存到DB上,再把 冪等Key 存到 Redis

顯然一般場景下 Redis 是無法保證冪等的

所以Redis只是一個前置處理,最終的冪等性依賴 DB唯一Key(訂單Id+訂單狀態)

總的來說就是:通過 Redis 做前置處理,DB 唯一索引做最終保證實現冪等性

訊息順序消費

訊息的順序性很好理解,還是以訂單處理為例

訂單的狀態有:支付、確認收貨、完成等等,而訂單下還有計費、退款的訊息報

理論上來說:支付的訊息肯定要比退款的訊息先到。

但是程式處理的過程就不一定了,所以我們處理訊息順序消費的流程如下:

  • 寬表:建立一張寬表,唯一索引是 訂單Id,將訂單的每個狀態拆分為一個列,當訊息來了,只更新對應的欄位就好,訊息只會存在短暫的狀態不一致問題,但是最終狀態是一致的
  • 訊息補償機制
  • 把相同的 userID/orderId 傳送到相同的 partition(因為一個 consumer 消費一個 partition)

相關文章