使用消息中間件產生的問題和解決方案

消息中間件,本身存在高可用的保障,目前發生的消息丟失情況主要是通過業務稽覈的方式,發現發送端和消費端消息量不對等,存在兩種場景的異常:

消息事物和數據庫事務的一致性問題:數據庫操作全部成功,事物進行提交,而消息事物提交失敗。
消息中間件磁盤損壞問題: 消息中間件壞區,導致消息丟失。
問題總覽:

  1. 消息可靠性,冪等性,可維護性。消息異常處理機制,消息積壓,

問題一:消息的可靠性

1、生產者弄丟了數據

消息發送時,由於網絡以及中間件,異常導致重試。
消息中間件不穩定,在發送時,發生鏈路被服務端中斷,導致提交失敗。
解決方案
(1) RabbitMQ 提供的事務功能,就是生產者發送數據之前開啓 RabbitMQ 事務channel.txSelect,然後發送消息,如果消息沒有成功被 RabbitMQ 接收到,那麼生產者會收到異常報錯,此時就可以回滾事務channel.txRollback,然後重試發送消息;如果收到了消息,那麼可以提交事務channel.txCommit。

// 開啓事務
channel.txSelect
try {
    // 這裏發送消息
} catch (Exception e) {
    channel.txRollback
    // 這裏再次重發這條消息
}
// 提交事務
channel.txCommit

(2)confirm 模式:在生產者那裏設置開啓 confirm 模式之後,你每次寫的消息都會分配一個唯一的 id,然後如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個 ack 消息,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息,會回調你的一個 nack 接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。
事務機制和 confirm 機制最大的不同在於,事務機制是同步的,你提交一個事務之後會阻塞在那兒,但是 confirm 機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息 RabbitMQ 接收了之後會異步回調你的一個接口通知你這個消息接收到了。

所以一般在生產者這塊避免數據丟失,都是用 confirm 機制的

2、mq弄丟了數據

mq掛了,消息中間件發生存儲壞塊,並進行重置後,導致消息丟失。
解決方案: 開啓了持久化機制以及備份
kafka解決方案:

  • 給 topic 設置 replication.factor 參數:這個值必須大於 1,要求每個 partition 必須有至少 2 個副本。
  • 在 Kafka 服務端設置 min.insync.replicas 參數:這個值必須大於 1,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯繫,沒掉隊,這樣才能確保 leader 掛了還有一個 follower 吧。
  • 在 producer 端設置 acks=all:這個是要求每條數據,必須是寫入所有 replica 之後,才能認爲是寫成功了
  • 在 producer 端設置 retries=MAX(很大很大很大的一個值,無限次重試的意思):這個是要求一旦寫入失敗,就無限重試,卡在這裏了。
3、消費端弄丟了數據

(1)RabbitMQ 如果丟失了數據,主要是因爲你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啓了,那麼就尷尬了,RabbitMQ 認爲你都消費了,這數據就丟了。
這個時候得用 RabbitMQ 提供的 ack 機制,簡單來說,就是你必須關閉 RabbitMQ 的自動 ack,可以通過一個 api 來調用就行,然後每次你自己代碼裏確保處理完的時候,再在程序裏 ack 一把。這樣的話,如果你還沒處理完,不就沒有 ack 了?那 RabbitMQ 就認爲你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的。
(2)Kafka 會自動提交 offset,那麼只要關閉自動提交 offset,在處理完之後自己手動提交 offset,就可以保證數據不會丟。

問題二:消費冪等性:重複消費

消費集羣在接收消息後,進行業務處理,當最後確認消費完成時,發生消息斷連的情況,導致消息再次被髮送給其他消費者。
方案:採用redis存儲msgId 消費之前判斷msgId是否存在,如果存在則消費過。消費完同時寫入數據庫msgId作爲唯一主鍵。數據量大還可以採用BloomFilter算法過濾。

重複消費的幾種場景:
1.生產者重複發送
生產者支持最大超時重發機制,生產者可能在超時消息中間件未應答的情況下,重發消息,導致生產消息重複。
2.消息中間自身機制
1)ActiveMq:網絡閃斷情況下,可能會導致broker端消息重複投遞;
2)RocketMq:偏移量異步刷新機制,爲了吞吐量,新增或者停止消費者都可能會導致偏移量刷新中斷,或者刷新不夠及時,導致消費重複。
消息去重建議:
消息去重的核心思路在消費者場景中,確保業務的冪等性或者消息消費時的去重判斷是確保消息重複情況下不被重複消費的理想方式。
1.業務層面解決
業務層面可以各自採用消息處理去重方式,基於物理DB等。
2.消息讀寫框架上幫助統一實現方式
消息消費者框架統一支持基於Redis的去重插件功能。
主要採用BloomFilter算法+redis來實現消息ID的統一去重。

問題三:消費順序性

一個queue只能有一個consumer。嫌效率問題的話:寫 N 個內存 queue,具有相同 key 的數據都到同一個內存 queue;然後對於 N 個線程,每個線程分別消費一個內存 queue 即可,這樣就能保證順序性。
RabbitMQ
拆分多個 queue,每個 queue 一個 consumer,就是多一些 queue 而已,確實是麻煩點;或者就一個 queue 但是對應一個 consumer,然後這個 consumer 內部用內存隊列做排隊,然後分發給底層不同的 worker 來處理。
Kafka
寫 N 個內存 queue,具有相同 key 的數據都到同一個內存 queue;然後對於 N 個線程,每個線程分別消費一個內存 queue 即可,這樣就能保證順序性。

消息積壓的解決方案

幾千萬條數據在 MQ 裏積壓。
1、可能是消費者故障了,如有故障先排查,確保其恢復消費速度
2、臨時搞個消息分發程序,停掉現有的consumer,緊急擴容consumer。這個程序部署上去消費積壓的數據,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的n個queue(ps:如果對消費順序性不考慮的話)。當然如果要考慮消費順序的話,在分發程序中要保證消息的順序性的話,就需要自定義規則對消息的分發到同一個queue中。比如,訂單系統中,一般場景來說只需要同一筆訂單編號的消息需要保證順序性,可以對訂單編號進行hash,保證相同的訂單編號分發到同一個queue。

總之:要保證順序性,一個queue只能有一個consumer。嫌效率問題的話:寫 N 個內存 queue,具有相同 key 的數據都到同一個內存 queue;然後對於 N 個線程,每個線程分別消費一個內存 queue 即可,這樣就能保證順序性。

當然,這個功能也是可以在消息中間件解決方案中作爲一個單獨的功能,自動一鍵部署對應queue的消費者,自動分發,維護一鍵解決。

mq 中的消息過期失效解決方案

假設你用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據會大量積壓在 mq 裏,而是大量的數據會直接搞丟。
這個情況下,就只能對消息進行批量重導,這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然後重新灌入 mq 裏面去。

維護問題:消息跟蹤、查看困難

  1. 消息軌跡跟蹤困難: 消息的狀態(已發送,已消費,等待中),收發時間,收發地址缺乏有效的跟蹤。
  2. 消息內容查看困難: 由於消息存在於中間件中,而且broker衆多,和數據庫相比,無法有效的查看消息內容。
  3. 處理能力度量困難: 每筆消息的處理時長,消費者的處理能力,缺乏有效的度量指標,對消息隊列的健康情況無法做出有效的判斷。
  4. 異常場景維護困難: 在異常場景下,相比傳統的數據庫,處理能力有待完善, 數據可操作性較強,而消息中間件可操作行弱。比如:異常重處理: 當消費方,出現消費異常(例如業務邏輯錯誤,導致部分消息消費報錯),需要進行快速重處理時;或者消息中間件存儲損壞情況下(消息出現丟失)的異常重試能力
  5. 積壓情況下的快速轉移:當消息出現大量積壓的情況下,如何做快速的轉移和分發,
  6. 方便的統計檢索能力: 能夠支持多種索引條件下的檢索,統計能力

問題:消息冪等性,穩定性,可維護性

消息冪等:提供統一的去重解決方案,消息消費者框架支持基於Redis的去重插件功能
消息軌跡監控:支持消息在生產者、消息中間件、消費者落地軌跡查詢,方便定位分析消息重複、丟失出現的故障點
消息持久化:消息使用客戶端統一支持消息持久化功能
統一提供消息持異常處理機制:消息使用客戶端統一提供異常控制機制功能
消息內容查詢:支持將統一持久化的內容,通過統一轉碼方式解析提供內容查詢,方便業務數據存在異常的定位分析
消息監控:提供消息消費軌跡、消息集羣性能監控的功能

總體解決方案

1.框架統一生產者異常處理和重試機制
(1)生產者業務處理髮送消息到消息中間件
(2)消息發送存在IO、超時等異常情況,框架捕獲該異常,重試發送該筆消息
(3)重試發送機制,默認嘗試3次在持續異常的情況下,同時支持可配置
(4)對於超出重試機制的情況,框架提供統一的異常處理接口,該接口包括記錄“主題、消息體、異常堆棧”,其中具體記錄處理異常類由業務配置
2.框架統一消費者異常處理和重試機制
(5)消費者框架從消息中間件連接中獲取並消費消息
(6)消費者客戶端會判斷業務處理返回結果,捕獲異常信息
(7)對於異常情況,框架客戶端支持重試投遞消息繼續消費,異常重試次數默認爲3次,支持可配置
(8)如果重試次數超出了重試配置數量,則將該消息寫入死信隊列,供後續處理
(9)異常重試失敗之後,框架層面統一捕獲該異常,通過統一的異常接口,捕獲並記錄異常信息,具體記錄處理類由業務配置
3.對於標準化異常進入死信隊列的數據

4.消息積壓場景自動分發和擴容
提前寫好自動分發程序,維護自動擴容,自動分發。

消息中間件平臺包括:
1.消息控制檯
消息中間件配置管理
可視化方式支持不同的開源消息中間件broker資源、主題和相應監控配置管理。
消息中間件監控管理
可視化方式支持對底層消息中間件監控,其中包括集羣資源、集羣的處理性能、客戶端接入量、客戶端處理性能、消息的流經軌跡和消息內容的監控和查詢。
系統管理
支持消息中間件平臺用戶、權限、租戶和認證管理。
2.消息客戶端
1)消息生產者
封裝統一的消息生產者框架,支持對消息中間件接入的認證、發送消息、消息統一序列化、切面日誌、異常處理的能力。
2)消息消費者
封裝統一的消息消費者框架,支持對消息中間件接入的認證、消費消息、消息統一反序列化、切面日誌、異常處理和去重處理的能力。
3.消息註冊中心
註冊中心主要實現消息中間件的核心路由、集中管控的能力,不同的消息中間件註冊中心實現機制不同,Msgframe主要基於不同的中間件適配訪問註冊中心的服務,統一屏蔽底層中間件差異性,爲上層控制檯提供統一的配置管理和客戶端訪問能力。
4.消息broker集羣
消息中間件的核心主要實現broker集羣能力,這部分不同的消息中間件有不同的實現集羣方式,ActiveMq、kafka、RocketMq等。
5.消息持久化和搜索
支持消息從消息中間件的生產、消費、broker三個關鍵點採集消息內容數據,通過消息採集模塊統一同步至指定存儲,基於存儲之上提供消息內容ES搜索能力,支持消息軌跡、消息內容、消息監控的分析查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章