Skip to content
BEE
Backend Engineering Essentials

[BEE-10001] 訊息佇列 vs 事件串流 ​

INFO

訊息佇列(RabbitMQ、SQS)與事件串流(Kafka、Kinesis)在語意、傳遞方式與使用情境上的根本差異。

背景 ​

分散式系統透過非同步訊息傳遞,將生產者與消費者解耦。目前存在兩種截然不同的模型:訊息佇列與事件串流。表面上兩者很相似——都是從一個程序將資料傳送到另一個程序——但在語意、資料保留,以及消費者與資料互動的方式上有本質差異。

選錯模型會帶來架構上的痛苦:修完 bug 之後無法重播佇列中的訊息;在 Kafka 上跑一個簡單的工作佇列則增加了毫無必要的複雜度。這個選擇必須是深思熟慮的結果。

參考資料:

原則 ​

目標是任務分配時,選擇訊息佇列。目標是保留可重播的事件紀錄時,選擇事件串流。

核心語意 ​

訊息佇列 ​

訊息佇列是一種點對點、消費即刪除(consume-and-delete)的通道。

  • 生產者將訊息寫入佇列。
  • 由某一個消費者取走(多個消費者競爭分攤負載)。
  • 消費者確認(ack)後,broker 刪除該訊息。
  • 沒有位置或偏移量(offset)的概念——訊息存在至被處理,然後消失。

核心保證: 每條訊息恰好被一個消費者處理一次(在正確使用確認機制的前提下)。

事件串流 ​

事件串流是一種僅附加(append-only)的有序日誌。

  • 生產者將事件附加到日誌(Kafka 術語中稱為 partition)。
  • 每個消費者群組(consumer group)維護自己的偏移量(offset)——即已讀取到的位置。
  • 多個消費者群組獨立讀取同一份日誌,互不干擾。
  • 訊息根據可設定的保留策略持續保存,不因消費而刪除。
  • 消費者可從任意過去的偏移量**重播(replay)**日誌。

核心保證: 每個消費者群組都會按照順序看到所有事件(在同一 partition 內),且可以隨時重讀歷史資料。

並排比較圖 ​

在佇列模型中,消費者確認訊息後,訊息即消失——其他消費者無法讀取,也無法重播。在串流模型中,日誌是共用的;每個消費者群組獨立推進自己的游標,資料持續保留,供新群組或重播使用。

訂單處理範例 ​

以訊息佇列處理 ​

[訂單服務] ──發布──> [queue: orders]
                          │
          ┌───────────────┴───────────────┐
          │                               │
     [Worker 1]                      [Worker 2]
   取走訂單 #42                    取走訂單 #43
   處理付款                        處理付款
   送出 ack ──────────────────────────────>
   訊息刪除                        訊息刪除

訂單 #42 由 Worker 1 處理。ack 之後,broker 中不再有此訊息。若付款服務有 bug 需要重新處理,已不可能——訊息已消失。

以事件串流處理 ​

[訂單服務] ──附加──> [topic: orders  | offset: 0  1  2  3  4  5 ]
                                              │  │  │  │  │  │
                      訂單服務讀取到 ─────────────────────────5
                      分析服務讀取到 ────────────────────3
                      審計服務讀取到 ──0  (從頭重播)

偏移量 4 的同一個事件被三個群組獨立消費。每個群組追蹤自己的位置。當分析管線修好 bug 部署後,將其偏移量重設為 0,從頭重播整段歷史,完全不影響訂單服務。

為何串流支援重播: 日誌是不可變的,依保留策略(例如 7 天或無限期)保存。消費動作不會刪除資料。佇列是消費即刪除:消費本身就是刪除的觸發器。

使用時機 ​

判斷依據訊息佇列事件串流
主要用途任務分配、工作佇列、命令處理事件溯源、EDA、審計記錄、分析管線
訊息存活期直到被消費(並確認)可設定的保留策略(時間或容量)
多消費者競爭取走同一條訊息(負載分攤)每個消費者群組獨立讀取
重播不支援支援——重設偏移量到任意過去位置
排序佇列層級的 FIFOPartition 內保證順序;跨 partition 無保證
消費者複雜度低(broker 處理路由)較高(消費者需管理偏移量)
背壓處理自然——佇列深度增加消費者延遲(consumer lag)需監控
典型系統RabbitMQ、Amazon SQS、ActiveMQApache Kafka、Amazon Kinesis、Redpanda

適合使用佇列的情境 ​

  • 每個任務只需一個 worker 處理。
  • 任務是獨立命令:發送 email、縮圖處理、信用卡扣款。
  • 不需要重播;在乎吞吐量與簡潔性。
  • 消費者池需要水平擴展以消化積壓。

適合使用串流的情境 ​

  • 多個獨立系統需要對同一事件做出反應。
  • 需要審計日誌、事件溯源,或從歷史重建狀態的能力。
  • 需要讓新消費者讀取歷史資料。
  • 正在構建以自己節奏運行的分析或資料管線。
  • 修 bug 或推出新功能後,可能需要重播事件。

保留策略與排序 ​

保留策略 ​

訊息佇列在消費者取走並確認後即保留結束。佇列是緩衝,而非紀錄。

事件串流依設定的策略保留訊息:

  • 時間策略:保留 7 天的事件。
  • 容量策略:每個 partition 保留最後 100 GB。
  • 壓縮策略:每個 key 只保留最新值(log compaction)。

此保留模型使串流適合作為系統記錄(system of record),而不僅僅是傳輸層。

排序 ​

兩種模型都提供排序,但範圍不同。

佇列: 訊息以 FIFO 順序從佇列送出。有競爭消費者時,兩個 worker 可能同時處理相鄰訊息,跨 worker 的順序無法保證。

串流: 在單一 partition 內,順序嚴格保證。跨 partition 無全域排序保證。若某個邏輯群組的事件需要有序(例如同一訂單 ID 的所有事件),必須透過一致的 partition key 將它們路由到同一 partition。

消費者群組與 Partition ​

Kafka 的消費者群組模型常被誤解,值得深入說明。

消費者群組(consumer group) 是一個邏輯訂閱者。同一群組內的所有消費者分攤工作:每個 partition 在任一時刻只被群組中的一個消費者分配。兩個群組讀取同一 topic 時互不干擾;各有獨立的偏移量。

Topic: orders(3 個 partitions)

消費者群組:payment-service
  Consumer 1 → Partition 0
  Consumer 2 → Partition 1
  Consumer 3 → Partition 2

消費者群組:analytics-service
  Consumer A → Partition 0, 1, 2(單一消費者讀取全部)

實際意涵:群組內的擴展受限於 partition 數量。一個群組中的活躍消費者數不能超過 partition 數。規劃 partition 數量時,需考量未來的吞吐量需求。

常見錯誤 ​

1. 需要重播時卻使用佇列 ​

如果下游服務可能需要重新處理事件——用於補填資料(backfill)、修 bug、或新功能追補——佇列無法滿足需求。訊息一旦被消費並確認,即永久消失。你只能選擇從來源重新發送事件,或另外維護一份事件資料庫。

2. 簡單任務分配卻使用串流 ​

Kafka 帶來額外的維運負擔:partition 規劃、偏移量管理、consumer lag 監控,以及 broker 叢集管理。對於簡單的後台工作佇列(發送 email、處理上傳),專用的佇列系統更簡單、更便宜、更易於維運。不要因為 Kafka 聽起來厲害就使用它。

3. 假設跨 Partition 有全域排序 ​

不同 partition 中的事件,只保證在同一 partition 內的順序。跨 partition 沒有排序保證。若業務邏輯依賴嚴格全域順序(例如「每個使用者所有事件的嚴格順序」),必須使用單一 partition(限制吞吐量),或設計消費者能容忍跨 partition 的亂序。

4. 未監控消費者延遲(Consumer Lag) ​

在串流中,慢速消費者不會阻塞生產者或其他消費者。相反地,消費者的偏移量落後,consumer lag 不斷增長。未監控的 consumer lag 意味著你的下游服務正在靜默地處理過時資料。將 lag 作為一級指標追蹤;超過閾值時發出告警。

5. 未使用確認就期望保證傳遞 ​

佇列和串流都支援傳遞保證,但都需要明確的確認行為:

  • 佇列:消費者在確認前崩潰,broker 重新投遞訊息。
  • 串流:消費者負責提交偏移量。若在處理完成前提交偏移量,消費者崩潰後重啟,該事件會被跳過。

明確設定確認與偏移量提交策略。至少一次傳遞(at-least-once,失敗時重處理)通常比恰好一次(exactly-once)更容易安全地實作。

相關 BEE ​

  • BEE-10002 — Pub/Sub 模式與扇出(fan-out)
  • BEE-10003 — 傳遞保證:至多一次、至少一次、恰好一次
  • BEE-10004 — 事件溯源:從事件日誌重建狀態
  • BEE-7004 — 訊息序列化格式(Avro、Protobuf、JSON)