返回文章列表
rabbitmq

Cluster 與 High Availability

介紹 RabbitMQ 叢集架構、節點類型、Quorum Queue 與 Classic Mirrored Queue 的差異及高可用性策略

Aaron

單節點 RabbitMQ 有 SPOF、吞吐上限、升級必須停機等問題。Cluster 把多個節點組成邏輯單一 broker,提供高可用、水平擴展、滾動升級。但要特別注意:RabbitMQ Cluster 預設只複製 metadata,不複製訊息

Cluster 基本架構

底層是 Erlang Distribution。組成條件是同一個 Erlang Cookie~/.erlang.cookie)、網路互通(25672 + 4369)、相同 Erlang/RabbitMQ 版本。節點發現可用 classic_config、DNS、Kubernetes Operator、Consul / etcd。

Metadata 複製 vs Message 複製

這是最常見的誤解。Cluster 預設同步 exchange、binding、queue 定義、user/vhost/permissions,但 queue 裡的訊息只存在宣告它的那個節點上。任何節點都看得到 queue,但該節點掛掉訊息就不可用——這就是為什麼需要 Quorum Queue(或已 deprecated 的 Mirrored Queue)。

節點類型

至少要有一個 Disk Node,否則所有節點重啟後 metadata 就丟了。實務上全部用 Disk Node 最安全,RAM Node 現在幾乎不用。

Classic Mirrored Queue(已 deprecated)

舊的 HA 方案,Master + Mirrors 架構。因為同步機制複雜、腦裂時可能丟訊息、大 cluster 下不穩定,從 3.9 開始正式 deprecated。新專案不要用。

Quorum Queue

RabbitMQ 從 3.8 開始把 Quorum Queue 當作所有生產 HA 場景的預設答案。它用的是 Raft 共識演算法,跟 etcd、Consul、CockroachDB 同一套理論基礎。

Raft 的角色

每個 Quorum Queue 宣告時指定成員(通常 3 或 5 個節點)形成一個 Raft group。寫入必須先送到 leader,leader append 到自己的 log 並推送給 followers;當超過半數成員已把 entry 寫到磁碟,leader 才標記為 committed 並回覆 publisher confirm。

  • Quorum = ⌊N/2⌋ + 1(3 需要 2、5 需要 3)
  • 可容忍故障 = N - Quorum(3 能掛 1、5 能掛 2)

Leader 掛掉時剩下的 follower 重新選舉,因為 log 在 commit 前已同步到多數節點,新 leader 一定擁有所有已 commit 的訊息——這就是強一致性的具體意義。

宣告

await channel.declare_queue(
    "orders",
    durable=True,  # Quorum Queue 必須 durable
    arguments={
        "x-queue-type": "quorum",
        "x-quorum-initial-group-size": 3,
        "x-queue-leader-locator": "balanced",
        "x-max-in-memory-length": 10000,
        "x-delivery-limit": 5,
    },
)

x-delivery-limit 是 Quorum Queue 獨有的 poison message 保護——超過重送上限自動丟到 DLX 或 drop,比手動實作重試計數可靠。

vs Mirrored Queue

特性MirroredQuorum
一致性最終一致強一致(Raft)
訊息保證失敗時可能丟多數節點存活就不丟
Failover 時間秒到分鐘< 1 秒
記憶體全部訊息留 RAM預設 lazy,直接進磁碟
Poison 防護x-delivery-limit
單筆寫入成本高(Raft quorum ack)
推薦× 3.9 deprecated○ 新專案標準

新專案一律 Quorum Queue,舊專案規劃一次性遷移

成員管理與 Rebalance

初學者的大坑:Quorum Queue 的成員是靜態的,不會隨 cluster 擴容自動跟著長大

宣告時 x-quorum-initial-group-size=3 會從當下可用節點挑 3 個寫進 queue metadata,這組成員從此固定。擴容 node4、node5 後,原本 100 個 queue 完全不會用到新節點——只有之後新宣告的 queue 才有機會放 member 到新節點。這是刻意的:自動重新分配意味大量資料複製與流量尖峰,RabbitMQ 寧可要你明確告訴它要搬去哪。

手動調整

rabbitmq-queues add_member -p / orders rabbit@node4
rabbitmq-queues delete_member -p / orders rabbit@node2
rabbitmq-queues quorum_status orders

add_member 不會立刻生效——新節點進入 non-voter 狀態:開始從 leader 拉 Raft log 補歷史,但不參與 quorum 計算、也不能當 leader。這設計避免「新節點一加入被算進 quorum 但還沒追完 log 卡住所有寫入」。log 追到接近 commit index 後自動升為 voter。擴容存量大的 queue 可能花幾分鐘追 log,一次一個觀察壓力。

批次操作

rabbitmq-queues grow rabbit@node4 all
rabbitmq-queues shrink rabbit@node2

grow / shrink 內建節流與錯誤處理。下線節點的 SOP:先 shrink → 確認每個 queue 仍有足夠副本 → 再 stop_app。沒 shrink 就停節點會把「3 個要 2 個」變「2 個要 2 個」,再掛一個就整個 queue 不可用。

Leader Locator

x-queue-leader-locatorclient-local(放在宣告 client 所連節點)和 balanced(放在 leader 數最少的節點,預設)。Leader 位置不是動態平衡的:節點維護重啟後 leader 會被選到別處,復活後不會自動搶回。維護後記得執行:

rabbitmq-queues rebalance quorum

把 leader 重新分散。

Quorum Queue 的效能代價

3 副本寫入時 leader append log → 推給 followers → 每個 follower fsync → 多數 ack → commit → publisher confirm。比 Classic 多出三塊成本:

  • fsync 放大:每個成員每個 batch 都必須 fsync,3 副本最少 3 次 fsync
  • 網路 RTT:同 AZ 0.1–0.5ms、跨 AZ 2–5ms 直接反映到 confirm 延遲
  • 寫入放大:1KB 訊息在磁碟上真的寫 3 次,加 WAL 與 segment 結構約 6–10 倍 byte 寫入

實測數字

指標Classic (durable)Quorum (3 副本)
單 queue 吞吐峰值40–60k msg/s15–25k msg/s
confirm P502–5 ms5–10 ms
confirm P9910–20 ms20–50 ms
端到端5–15 ms15–40 ms
磁碟寫入量1x3–5x

Quorum 約 Classic 的 1/3 到 1/2 吞吐、延遲 2–3 倍。這不是實作問題是 Raft 的本質代價,etcd、CockroachDB 量級類似。對訂單、支付、通知這類訊息完全夠用;只有每秒幾十萬訊息或延遲要壓在 5ms 以下才會是問題——那類場景本來就不該用 RabbitMQ。

WAL 與 Segment

每個節點所有 Quorum Queue 共用同一個 WAL(每 vhost 一份),順序寫對磁碟友善,也是維持合理吞吐的關鍵。WAL 預設 512MB,寫滿觸發 flush 整理成各 queue 獨立的 segment file。Flush 期間 IOPS 需求高,throughput 曲線會週期性掉一小格。

監控重點:raft_commit_latency(應穩定在個位數 ms,>20ms 就查磁碟)、WAL flush 頻率、segment 總大小增長。

記憶體行為

Quorum Queue 預設就是 lazy——訊息寫完 WAL 從記憶體釋放,consumer 取用時才讀回來。所以不需要也不應該再設 x-queue-mode=lazyx-max-in-memory-length / x-max-in-memory-bytes 讓最新 N 筆同時留在記憶體避免 hot 訊息的磁碟 seek。消費速度接近生產時調大能降延遲,深 queue 則調小避免記憶體尖峰。

硬體與調校

  • 磁碟必須 SSD,最好 NVMe:HDD fsync 延遲常 >10ms,單 queue 吞吐會掉到 1–2k。雲端 EBS / PD 是網路磁碟,fsync 可能比本地 HDD 還差,WAL 要放本地 SSD。
  • 跨 AZ 評估延遲:同 AZ 0.1–0.5ms 可接受,跨 region 10–100ms 基本不可用,應改用 Federation/Shovel。
  • 控制 queue 數量:每個 Quorum Queue 都有一組 Raft process,幾百個沒問題,幾千個光 heartbeat 的 CPU 就很可觀。多時考慮合併後用 routing key 區分。

什麼時候不該用

  • 高頻可丟訊息(報價推送、metrics):花三倍 IO 保證不需要保證的東西,用 Classic non-durable 或 Stream
  • 大量短生命週期 queue(RPC reply queue、exclusive queue):Raft group 初始化成本比 queue 壽命還長
  • 單機 / dev:quorum size = 1 失去意義還更慢
  • 單 queue 要幾十萬 msg/s:超過 Quorum 設計上限,該水平分片或換 Kafka
  • P99 < 5ms:Raft 固有延遲調不下去

Stream Queue

RabbitMQ 3.9 引入的類 Kafka log-based queue,訊息可重播、極高吞吐,適合 event streaming。但如果需求真的是「類 Kafka」,直接用 Kafka 往往更合適。

網路分區

策略行為適用
ignore不處理單 AZ 內
autoheal自動選 winner、重啟 loser小 cluster
pause_minority少數派自動暫停推薦
pause_if_all_down看不到指定節點就暫停特殊場景

生產推薦 pause_minority,避免雙邊都接受寫入造成訊息分裂。

cluster_partition_handling = pause_minority

客戶端連線

connection = await aio_pika.connect_robust(
    "amqp://user:pass@rabbit1:5672,rabbit2:5672,rabbit3:5672/"
)

aio_pika/pika 支援多節點連線字串自動切換。也可以前置 HAProxy/雲端 LB,或用 DNS 多 A 紀錄(較粗糙)。

部署實務

節點數用奇數——Raft 需多數決,偶數沒好處還更容易腦裂。3 個(容忍 1 掛)入門;5 個(容忍 2 掛)高可用;7 個大型服務少見。

跨 AZ 部署優點是 AZ 級容錯,缺點是 AZ 間延遲影響 Quorum 寫入,建議 3 或 5 節點每 AZ 一個並搭配 pause_minority。Kubernetes 上用官方的 RabbitMQ Cluster Operator