🌐 This page hasn't been translated yet — showing the original Chinese. Translated posts

RabbitMQ:訊息 broker 的叢集與流控

· tech

#infrastructure#rabbitmq

📑 目錄

收尾「有狀態的重量級」這一批,第三個是 RabbitMQ。它跟 Kafka 都是訊息中介,但 infra 形狀差很多,而差別可以濃縮成一個字:Kafka 是 log,RabbitMQ 是 queue。 這個字的差別,讓它們的狀態、擴展、故障全走向了不同的方向。

log vs queue:一個字的差別,兩種 infra

Kafka:log m1m2m3m4m5 B 讀到 m2 A 讀到 m4 訊息留著 · 各自用 offset 讀 可重播 · 多消費者 fan out RabbitMQ:queue m4m3m2 consumer m1 已被取走 + ack → 消失 消費即移除 · broker 追蹤 ack 複雜路由 · per-message 控制 狀態:Kafka = 一條「消費留痕」的 log(磁碟為王) · RabbitMQ = queue 裡待處理的訊息(消費即減少) 取捨:事件流 / 可重播 / High-throughput → Kafka · 任務佇列 / 複雜路由 / per-message → RabbitMQ
Kafka(log):訊息寫進去就留著,每個 consumer 用自己的 offset 記錄讀到哪、互不干擾——所以能重播、能多消費者各自 fan out。RabbitMQ(queue):訊息排隊等人拿,被取走並 ack 之後就從 queue 消失,由 broker 逐筆追蹤誰 ack 了沒。這個「留著 vs 拿走」的模型差,直接決定了兩者的脾氣:Kafka 適合 High-throughput 的事件流,RabbitMQ 適合要複雜路由、要 per-message 控制(優先級、延遲、逐筆重試)的任務分派

從 infra 的角度,這個模型差最關鍵的後果是狀態的形狀不同:Kafka 的狀態是一條只增不減、以磁碟 throughput 為王的 log;RabbitMQ 的狀態是一堆 queue 裡「還沒被處理掉」的訊息——它會隨消費而減少、隨 backlog 而膨脹。而正是這個「會膨脹的 queue」,埋下了 RabbitMQ 最招牌的坑。

RabbitMQ 的招牌坑:queue backlog 與 backpressure

RabbitMQ 的頭號故障模式,是 queue backlog——一旦 consumer 跟不上 producer,queue 就會越積越大,而 RabbitMQ 有一套自我保護機制會在此時啟動:

queue backlog → 撞 watermark → backpressure 擋住 publisher Publisher Queue backlog ↑↑消費跟不上,越積越大撞 memory/disk watermark Consumer(慢,跟不上) ⚠ alarm block publisher(backpressure)——上游暫時寫不進去 這是自我保護(免得 broker OOM 撐爆),但對上游是「突然寫不進去」 解法:監控 queue depth、確保消費跟得上、設 queue 上限 / TTL / dead-letter
當 consumer 跟不上、queue 一路堆高,broker 的記憶體或磁碟用量會撞到 watermark 水位線、觸發 alarm,接著 RabbitMQ 會反過來阻擋 publisher(flow control)——這是一種 backpressure,寧可讓上游暫時寫不進去,也不讓 broker 自己被撐爆 OOM。它是好的自我保護,但如果你沒在監控 queue depth,第一個察覺的方式往往是「上游突然全部寫入失敗」。所以 RabbitMQ 的維運核心,就是盯住 queue 別讓它積起來

HA、容量、在 k8s 上

  • HA:用 quorum queue,別用舊的 mirrored。要讓 queue 本身不因單節點掛掉而丟訊息,現代做法是 quorum queue——底層是 Raft,過半副本確認才算數,取代了舊的 classic mirrored queue(同步慢、故障時可能丟訊息,已被淘汰)。又是共識在真實系統裡的一次現身。
  • 容量與擴展:瓶頸是記憶體 + 磁碟(那兩道 watermark)。要注意 queue 本身難水平擴——一個 queue 綁在一個節點上,單 queue 的 throughput 受單節點限制;要更 High-throughput 得靠多 queue 分流,或讓多個 consumer 並行搶同一個 queue(competing consumers)。
  • 監控:queue depth(backlog 深度)是第一指標,再來是消費速率、unacked 訊息數、memory/disk alarm 狀態、connection/channel 數。
  • 在 k8s 上:跟其他有狀態工具一樣——StatefulSet + PV 放持久化訊息、給 cluster 節點穩定身分互相發現;官方的 RabbitMQ Cluster Operator 幫你管這些。

反思

一個字的模型差,撐開兩套完全不同的 infra

「Kafka 是 log,RabbitMQ 是 queue」——這句話我以前當成一個瑣碎的技術細節,直到從 infra 角度重看,才發現它是一切的分水嶺。留著 vs 拿走,這一個模型上的選擇,像骨牌一樣推倒了後面所有 infra 決策:狀態的形狀(只增的 log vs 會膨脹收縮的 queue)、故障模式(Kafka 是磁碟塞爆 vs RabbitMQ 是 queue backlog backpressure)、擴展方式(Kafka 分 partition vs RabbitMQ 分 queue)。這再次印證了體檢表那個核心信念——看懂一個工具最根本的資料模型,它的整個 infra 形狀就跟著決定了。而反過來,選型時也該從這裡切入:不是問「Kafka 和 RabbitMQ 哪個好」,而是問「我要的是 log 還是 queue」。

好的系統會「保護自己」,而不是硬撐到爆

RabbitMQ 的 backpressure 機制,我一開始覺得很煩——上游好好的怎麼突然寫不進去了?但想通之後反而很欣賞它:一個成熟的系統,在快撐不住的時候,會選擇擋住入口、保護自己,而不是默默吞到記憶體爆掉、整台崩潰。 「寧可拒絕新的、也不讓自己死掉」,這其實跟我在 連鎖失效那篇講的 load shedding、 backpressure 是同一種智慧——過載時主動、優雅地把壓力擋在門外,遠比硬撐到雪崩好。這個觀念我後來套用到很多地方:限流、熔斷、甚至個人的工作量管理——懂得在滿載前說「我先擋一下」,是系統和人共通的成熟。

選型是選「模型」,不是選「誰比較強」

做過這幾篇有狀態工具的對照,我對「技術選型」的理解變得更乾淨了:很多時候,兩個工具不是「一個強一個弱」,而是體現了兩種不同的模型、服務兩種不同的需求。Kafka 和 RabbitMQ 就是最好的例子——它們不是競品,是為不同問題長出來的不同形狀。硬要比「哪個好」,就像問「螺絲起子和鎚子哪個好」一樣沒意義;該問的是「我手上這顆,是螺絲還是釘子」。先把自己的問題看清楚(要 log 還是 queue、要 throughput 還是要路由),答案自己就浮出來了——這比追逐「業界最推薦哪個」有用太多。這也是我做完這批有狀態工具,最想留下的一句話。