返回文章列表
rabbitmq

Message Persistence & Durability

探討如何透過 Durable Queue、Persistent Message 與 Publisher Confirms 三者搭配,確保訊息在 Broker 重啟後不遺失

Aaron

RabbitMQ 自己不保證持久性

RabbitMQ 預設是效能優先:訊息放記憶體、queue 重啟消失、publish 不等確認、consumer auto ack。想要可靠性,必須逐項啟用對應機制,而且這不是 broker 單方能做到的事,而是 producer、broker、consumer 三方合作的結果。

訊息不遺失的三個必要條件

要讓一則訊息從 producer 到 consumer 全程不遺失,必須同時滿足:

  1. Durable Queue:queue 定義寫入磁碟,broker 重啟後還在。
  2. Persistent Message:訊息標記 delivery_mode=2,broker 才會寫磁碟。
  3. Publisher Confirms:producer 等 broker ack 才算送達。

三者是乘法關係:只做 durable queue 但訊息非 persistent,重啟後 queue 還在但空了;只做 persistent 但 queue 非 durable,queue 自己都沒了;前兩項都做但沒有 confirm,producer 無法知道訊息有沒有真的到 broker。

Durable Queue

durable=True 只保證 queue 的定義(名字、arguments、bindings)重啟後還在,不保證 queue 裡的訊息還在——訊息能不能留住取決於它是不是 persistent。

await channel.declare_queue("orders", durable=True)

Durable 屬性一經宣告就不能改,要改只能刪掉重建。實務上的建議是新 queue 永遠預設 durable=True,除非是 RPC reply queue 這類短命 queue。

Persistent Message

透過 delivery_mode 控制:

await exchange.publish(
    aio_pika.Message(
        body=b"order #123",
        delivery_mode=aio_pika.DeliveryMode.PERSISTENT,  # = 2
    ),
    routing_key="order.created",
)
delivery_mode語意場景
1 (Transient)只在記憶體可容忍遺失的監控、即時通知
2 (Persistent)寫入磁碟業務訊息、訂單、支付

兩個細節:persistent message 送進 non-durable queue 時 broker 不會寫磁碟,所以 durable queue 與 persistent message 必須成對;還有 persistent 不代表立刻 fsync,下一節細談。

Publisher Confirms

前兩個條件只解決「訊息到了 broker 之後」的問題,從 producer 到 broker 的網路傳輸、以及 broker 收到但還沒落盤的空窗,都還可能出事。Publisher Confirms 讓 broker 在訊息真的完成處理(包括寫磁碟)後回 basic.ack,producer 在收到 ack 前必須保留訊息並在失敗時重發。細節與三種模式見 Publisher Confirms

嚴格來說,Publisher Confirms 是「驗證前兩個條件有沒有真的生效」的唯一手段——ack 本身就代表 broker 已經完成 durable queue 裡的 persistent message 寫入。

fsync 與效能的取捨

Broker 收到 persistent message 時會把內容寫入 OS page cache 並更新 index,但不會立刻 fsync。因為 fsync 太貴:SSD 約 0.1–1ms、HDD 5–20ms,每筆都 fsync 吞吐會被磁碟嚴格限制。Broker 改用批次 fsync,累積一定數量或時間才刷一次,吞吐回到可接受水準。

代價是一個小小的風險窗口:訊息已經收下甚至已回 confirm ack,但在下次 fsync 前整台機器斷電(不是程序 crash,是 OS 也死掉),page cache 的資料會一起消失。窗口通常幾十到幾百毫秒,對多數業務可忽略,但對金流、醫療、法律這類「絕對不能遺失」的場景不夠——需要的是多節點複製,也就是 Quorum Queue

設定組合可靠性吞吐相對值場景
Transient + Non-durable重啟即失100%監控採樣
Persistent + Durable(無 confirm)重啟不失但不可知~70%不推薦
Persistent + Durable + Confirms單節點強保證~50%業務訊息合理預設
加 Quorum Queue(3 副本)多節點強保證~25–35%金流、關鍵事件
再加跨 AZ 部署資料中心級容錯~15–25%核心系統

每上一層可靠性大約打 5–7 折吞吐。成熟做法是訊息分級,不同 queue 用不同設定

Persistent Message vs Lazy Queue

兩者都寫磁碟但目的完全不同。

特性Persistent MessageLazy Queue
目的防止重啟遺失防止記憶體壓力
寫磁碟時機延遲批次立刻
記憶體使用預設仍留在記憶體盡快釋放
場景業務訊息標準設定可能大量堆積的 queue

兩者可以同時使用。另外 Quorum Queue 預設就是 lazy 行為——訊息直接寫 Raft log 不留記憶體,所以用 Quorum Queue 時不需要也不應該再設 x-queue-mode=lazy

可靠性是一條鏈

即使 producer 有 confirms、broker 有 durable + persistent + Quorum Queue,consumer 端還有兩件事要做:manual ack(auto ack 下訊息離開 queue 就永遠消失,即使處理失敗也無法重送)、冪等處理(crash 重送可能讓同一則訊息處理多次,見 Idempotency)。

完整鏈:

  • Producer → broker:Publisher Confirms + mandatory flag
  • Broker 落盤:durable queue + persistent message
  • 跨節點複製(可選):Quorum Queue
  • 主 queue → DLX:沒有 publisher confirm,只能靠正確配置 + 測試 + 監控(見 DLX
  • Broker → consumer:manual ack
  • Consumer 處理:冪等

任何一環沒做好整條鏈就斷了。