← 回文章列表
2025年9月16日
Kafka 與 RabbitMQ 差在哪?從「佇列」和「日誌」兩種模型看怎麼選
前面分別寫過 RabbitMQ 的 Exchange、Queue、Binding、RabbitMQ 的 Ack 與死信佇列,以及 Kafka 完整入門、Kafka 實務注意事項。兩個我都在專案中用過,最常被問的問題就是:「那到底要用哪一個?」
答案要從它們最根本的差異開始講。
核心差異:佇列 vs 日誌
RabbitMQ 是佇列(Queue):
- Broker 負責路由:Producer 交給 Exchange,Exchange 依 Binding 決定複製到哪些 Queue
- Broker 主動推送訊息給 Consumer,並追蹤每一則訊息的狀態(未投遞、處理中、已 Ack)
- Consumer Ack 之後,訊息就從 Queue 刪除
- 一句話:聰明的 Broker、單純的 Consumer
Kafka 是日誌(Log):
- Producer 只是把訊息附加到 Partition 的尾端,Broker 不做路由
- Consumer 自己拉取,自己記錄讀到哪個 Offset,Broker 不追蹤個別訊息
- 訊息讀完不刪除,依保留期限或容量清除,任何人都可以從任何位置重讀
- 一句話:單純的 Broker、聰明的 Consumer
幾乎所有其他的差異,都是從這裡推出來的。
逐項比較
| RabbitMQ | Kafka | |
|---|---|---|
| 模型 | 佇列,處理完就刪除 | 日誌,依保留期限清除 |
| 投遞方式 | Broker 推送(Push) | Consumer 拉取(Pull) |
| 處理進度 | Broker 追蹤每則訊息的 Ack | Consumer Group 記錄 Offset |
| 路由 | Exchange + Binding,可依 Routing Key、萬用字元過濾 | 只有 Topic,過濾要在 Consumer 端做 |
| 重讀歷史訊息 | 不行,Ack 後就消失 | 可以,把 Offset 倒回去 |
| 分工的平行度 | 同一個 Queue 加 Consumer 就好,沒有上限 | 受 Partition 數量限制 |
| 順序 | 單一 Queue 單一 Consumer 時有序 | Partition 內有序,用 Key 控制 |
| 單則失敗處理 | Nack、Requeue、死信佇列,內建 | 沒有單則 Ack,重試與死信要自己做 |
| 延遲訊息 | TTL + 死信,或 Delayed Message 外掛 | 沒有內建 |
| 優先權 | 支援 Priority Queue | 不支援 |
| 吞吐量 | 每秒數萬筆等級 | 每秒數十萬到百萬筆等級 |
| 訊息堆積 | 堆積太多會影響效能,Queue 應該保持接近空 | 堆積是常態,資料本來就存在磁碟上 |
同一個需求,兩邊怎麼做
分工:多個 Worker 一起處理
- RabbitMQ:多個 Consumer 連到同一個 Queue(Competing Consumers)。工作量大就加 Consumer,沒有上限,搭配 Prefetch 讓慢的 Worker 少拿一點
- Kafka:多個 Consumer 使用同一個
group.id。平行度上限是 Partition 數量,Consumer 比 Partition 多的部分只會閒置
所以像「產生報表」「轉檔」這種每件工作要跑好幾秒到好幾分鐘、數量又會暴增的任務,RabbitMQ 隨時加 Worker 的彈性比較好用。
廣播:每個服務都要收到
- RabbitMQ:每個服務各自宣告一個 Queue,綁到同一個 Fanout 或 Topic Exchange
- Kafka:每個服務使用不同的
group.id訂閱同一個 Topic
兩者都能做,但 Kafka 的優勢是:新加入的服務可以從頭讀起,拿到上線之前的歷史事件;RabbitMQ 的新 Queue 只會收到綁定之後的訊息。
只收某些訊息:路由與過濾
- RabbitMQ:Topic Exchange 的 Binding Key 用萬用字元過濾,例如
house.*.franchise只收加盟物件,不需要的訊息根本不會送到 Consumer - Kafka:Consumer 會讀到整個 Topic,不需要的訊息要自己丟掉。如果某類訊息只有少數 Consumer 關心,通常會拆成獨立的 Topic
順序
- RabbitMQ:單一 Queue 的訊息是先進先出,但只要有多個 Consumer 或發生 Requeue,順序就不保證。要嚴格有序,只能一個 Queue 一個 Consumer,等於放棄平行處理
- Kafka:用 Key 讓同一個實體的訊息進同一個 Partition,同一個實體有序、不同實體平行,這是 Kafka 的強項
處理失敗的訊息
- RabbitMQ:每則訊息獨立 Ack,失敗的那一則可以 Nack 送進延遲重試或死信佇列,其他訊息照常處理。這些機制都是內建的
- Kafka:Consumer 提交的是「讀到哪個位置」,沒有「單獨標記某一則失敗」的概念。失敗的訊息要嘛原地重試、卡住整個 Partition,要嘛自己發到重試 Topic、死信 Topic,全部要自己設計
延遲處理
「訂單 30 分鐘沒付款就自動取消」這種需求:
- RabbitMQ:TTL 加死信,或安裝 Delayed Message 外掛,訊息到時間才投遞
- Kafka:沒有內建,通常要另外搭配排程或資料庫
事後重新處理
發現上週的資料處理錯了,修好程式要重跑一次:
- RabbitMQ:訊息早就刪掉了,只能從資料庫或其他來源重新產生
- Kafka:只要還在保留期限內,把 Consumer Group 的 Offset 倒回上週,重新消費一次就好
怎麼選
選 RabbitMQ 的情境:
- 背景工作佇列:寄信、產生 PDF、轉檔,每件工作獨立、處理時間長短不一,需要隨時增加 Worker
- 需要複雜路由:依事件類型、來源、地區把訊息分給不同的系統,讓 Broker 幫忙過濾
- 每則訊息要個別處理成敗:失敗的要重試、重試不了的進死信佇列,不能卡住其他訊息
- 需要延遲、優先權、RPC(Request/Reply)這類訊息層級的功能
- 訊息量是每秒數千到數萬筆,處理完就不需要再看
選 Kafka 的情境:
- 大量的事件串流:日誌、監控指標、點擊紀錄、IoT 感測資料,每秒數十萬筆
- 同一份資料要給很多系統用,而且新系統要能補讀歷史資料
- 需要重播:修正錯誤後重新處理、用事件重建讀取模型
- 同一個實體的事件需要有序,又需要大量平行處理,例如帳務、交易事件
- CDC(Change Data Capture):把資料庫的異動串流到其他系統
如果一句話:要「把工作交出去」用 RabbitMQ,要「把發生的事情記下來,讓大家各自去讀」用 Kafka。
使用情境:一個系統同時用兩個
兩者並不互斥。以一個電商的下單流程為例:
- 訂單建立、付款、出貨等業務事件寫進 Kafka 的
order-events,以訂單 ID 為 Key:- 搜尋與報表服務各自用不同的
group.id消費,建立自己的讀取模型 - 資料分析平台從 Kafka 取得完整的歷史事件
- 讀取模型的邏輯改了,就把 Offset 倒回去重建
- 搜尋與報表服務各自用不同的
- 「寄送訂單確認信」「產生電子發票 PDF」這類背景工作丟進 RabbitMQ:
- Worker 數量隨工作量隨時增減
- 寄信失敗就延遲重試,信箱格式錯誤就進死信佇列,不影響其他人的信
- 「30 分鐘未付款自動取消」用延遲訊息處理
判斷的方式是看這則訊息的本質:它是「一件已經發生、很多人可能有興趣的事實」,還是「一件需要有人去完成的工作」。
注意事項
- 不要把 Kafka 當工作佇列用:處理時間長的工作容易超過
max.poll.interval.ms觸發 Rebalance;一則失敗會卡住整個 Partition;Worker 數量又受 Partition 限制。這些都是 RabbitMQ 擅長的 - 不要把 RabbitMQ 當事件儲存用:Queue 裡堆積大量訊息會吃記憶體與磁碟、拖慢效能,它的設計是讓 Queue 盡量保持接近空的狀態。需要長期保存、重播的,交給 Kafka 或資料庫
- 兩邊的「至少一次」都一樣:不管是 RabbitMQ 的手動 Ack 還是 Kafka 的先處理再提交 Offset,訊息都可能被處理不只一次,Consumer 一定要冪等
- 維運成本不同:RabbitMQ 單機就能跑起來,管理介面直覺;Kafka 需要規劃 Partition、複本、保留期限、磁碟容量,叢集維運的門檻比較高。訊息量不大、團隊又沒有 Kafka 經驗時,不要只因為「Kafka 比較快」就選它
- 兩者都在互相靠近:RabbitMQ 3.9 加入了 Streams,提供類似 Kafka 的可重讀日誌;Kafka 4.0 也以 Early Access 推出了 Share Group(KIP-932),讓多個 Consumer 可以不受 Partition 限制地分工、逐筆確認。不過這些都還是各自的「延伸功能」,選型時還是以核心模型為主
一句話總結
RabbitMQ 是聰明的 Broker 加處理完就刪除的佇列,擅長路由、逐筆確認、延遲與重試,適合把工作交出去;Kafka 是單純的 Broker 加可以重讀的日誌,擅長大吞吐量、多系統各自消費、依 Key 有序與重播,適合把發生的事記下來讓大家各自讀;分清楚訊息是「工作」還是「事實」,就知道該放在哪裡。