RabbitMQ 消息順序、消息冪等、消息重複、消息事務、集羣

1. 消息順序

場景:好比下單操做,下單成功以後,會發布建立訂單和扣減庫存消息,但扣減庫存消息執行會先於建立訂單消息,也就說前者執行成功以後,才能執行後者。html

不保證徹底按照順序消費,在 MQ 層面支持消息的順序處理開銷太大,爲了極少許的需求,增長總體上的複雜度得不償失。apache

因此,仍是在應用層面處理比較好,或者業務邏輯進行處理緩存

應用層解決方式:網絡

  • 1. 消息實體中增長:版本號 & 狀態機 & msgid & parent_msgid,經過 parent_msgid 判斷消息的順序(須要全局存儲,記錄消息的執行狀態)。
  • 2. 「同步執行」:當一個消息執行完以後,再發佈下一個消息。

2. 消息冪等、消息重複、消息事務

消息重複

形成消息重複的根本緣由是:網絡不可達。只要經過網絡交換數據,就沒法避免這個問題。因此解決這個問題的辦法就是繞過這個問題。那麼問題就變成了:若是消費端收到兩條同樣的消息,應該怎樣處理?異步

消費端處理消息的業務邏輯保持冪等性。分佈式

保證每條消息都有惟一編號且保證消息處理成功與去重表的日誌同時出現。性能

第 1 條很好理解,只要保持冪等性,無論來多少條重複消息,最後處理的結果都同樣。第 2 條原理就是利用一張日誌表來記錄已經處理成功的消息的 ID,若是新到的消息 ID 已經在日誌表中,那麼就再也不處理這條消息。優化

第 1 條解決方案,很明顯應該在消費端實現,不屬於消息系統要實現的功能。第 2 條能夠消息系統實現,也能夠業務端實現。正常狀況下出現重複消息的機率其實很小,若是由消息系統來實現的話,確定會對消息系統的吞吐量和高可用有影響,因此最好仍是由業務端本身處理消息重複的問題,這也是 RabbitMQ 不解決消息重複的問題的緣由。.net

RabbitMQ 不保證消息不重複,若是你的業務須要保證嚴格的不重複消息,須要你本身在業務端去重。設計

AMQP 消費者確認機制

AMQP 定義了消費者確認機制(message ack),若是一個消費者應用崩潰掉(此時鏈接會斷掉,broker 會得知),可是 broker 還沒有得到 ack,那麼消息會被從新放入隊列。因此 AMQP 提供的是「至少一次交付」(at-least-once delivery),異常狀況下,消息會被重複消費,此時業務要實現冪等性(重複消息處理)。

AMQP 生產者事務

對於生產者,AMQP 定義了事務(tx transaction)來確保生產消息被 broker 接收併成功入隊。TX 事務是阻塞調用,生產者需等待broker寫磁盤後返回的確認,以後才能繼續發送消息。事務提交失敗時(如broker宕機場景),broker並不保證提交的消息所有入隊。

TX 的阻塞調用使 broker 的性能很是差,RabbitMQ 使用 confirm 機制來優化生產消息的確認。Confirm 模式下,生產者能夠持續發送消息,broker 將消息批量寫磁盤後回覆確認,生產者經過確認消息的ID來肯定哪些已發送消息被成功接收。Confirm 模式下生產者發送消息和接受確認是異步流程,生產者須要緩存未確認的消息以便出錯時從新發送。

總結

  • 1. 消息重複發佈:不存在,由於 AMQP 定義了事務(tx transaction)來確保生產消息被 broker 接收併成功入隊。TX 事務是阻塞調用,生產者需等待 broker 寫磁盤後返回的確認,以後才能繼續發送消息。事務提交失敗時(如 broker 宕機場景),broker 並不保證提交的消息所有入隊。RabbitMQ 使用 confirm 機制來優化生產消息的確認(能夠持續發佈消息,但會批量回復確認)。
  • 2. 消息重複消費:AMQP 提供的是「至少一次交付」(at-least-once delivery),異常狀況下,消息會被重複消費,此時業務要實現冪等性(重複消息處理)。

應用層解決方式:

  • 1. 專門的 Map 存儲:用來存儲每一個消息的執行狀態(用 msgid 區分),執行成功以後更新 Map,有另外消息重複消費的時候,讀取 Map 數據判斷 msgid 對應的執行狀態,已消費則不執行。
  • 2. 業務邏輯判斷:消息執行完會更改某個實體狀態,判斷實體狀態是否更新,若是更新,則不進行重複消費。

特別說明:AMQP 協議中的事務僅僅是指生產者發送消息給 broker 這一系列流程處理的事務機制,並不包含消費端的處理流程。

3. 集羣

原 RabbitMQ 集羣:manager一、manager二、manager3 節點均爲磁盤存儲,manager1 爲主節點,HAProxy 負載三個節點。

現 RabbitMQ 集羣更新(更合理的配置):

  • 1. RabbitMQ 集羣更新:manager一、manager2 節點類型改成 ram(內存存儲),manager3 節點類型爲 disc(磁盤存儲,用於保存集羣配置和元數據),主節點變動爲 manager3。
  • 2. HAProxy 負載更新:移除 manager3 負載(5672 端口),只保留 manage二、manager2 負載。

4. Kafka 和 RabbitMQ 對比

Kafka 的設計有明確的介紹:http://kafka.apache.org/documentation.html#design

Kafka 應對場景:消息持久化、吞吐量是第一要求、狀態由客戶端維護、必須是分佈式的。Kafka 認爲 broker 不該該阻塞生產者,高效的磁盤順序讀寫可以和網絡 IO 同樣快,同時依賴現代 OS 文件系統特性,寫入持久化文件時並不調用 flush,僅寫入 OS pagecache,後續由 OS flush。

這些特性決定了 Kafka 沒有作「確認機制」,而是直接將生產消息順序寫入文件、消息消費後不刪除(避免文件更新),該實現充分利用了磁盤 IO,可以達到較高的吞吐量。代價是消費者要依賴 Zookeeper 記錄隊列消費位置、處理同步問題。沒有消費確認機制,還致使了 Kafka 沒法瞭解消費者速度,不能採用 push 模型以合理的速度向消費者推送數據,只能利用 pull 模型由消費者來拉消息(消費者承擔額外的輪詢開銷)。

若是在 Kafka 中引入消費者確認機制,就須要 broker 維護消息消費狀態,要作到高可靠就須要寫文件持久化並與生產消息同步,這將急劇下降 Kafka 的性能,這種設計也極相似 RabbitMQ。若是不改變 Kafka 的實現,而是在 Kafka 和消費者之間作一層封裝,仍是須要實現一套相似 RabbitMQ 的消費確認和持久化機制。

參考資料:

相關文章
相關標籤/搜索