🎯 什麼情境該想到我

當你「要確保一則訊息恰好被一個接收者消費一次」的時候(例如下一筆命令、派一件工作,不能被重複執行)。

⚙️ 怎麼用(步驟 / 公式)

意圖:用一個保證「每則訊息只被一個接收者取用」的通道來傳送訊息。

做法 / 結構要點:

  • 通道上可以有多個接收者,但每則訊息只會交給其中一個。
  • 多個接收者可用來分攤負載(競爭消費者),彼此瓜分訊息、各做各的。
  • 適合命令式或工作分派的訊息,強調「只處理一次」。

★ 各家 broker 怎麼做到「一則訊息只給一個接收者」

broker機制★ 關鍵限制
RabbitMQqueue + 多個 consumer(round-robin/prefetch)★ 順序不保證(多個 consumer 並行處理)
SQSqueue + visibility timeout★ 標準佇列不保證順序也可能重複;FIFO 佇列有吞吐上限
Kafkatopic + 同一個 consumer group(每個分區只指派給組內一個成員)★★ 並行度上限 = 分區數
NATS JetStreamqueue group/durable consumer—

★★ Kafka 的關鍵差異:並行度被分區數鎖死

topic 有 3 個分區,consumer group 有 5 個成員
→ ★ 3 個成員各拿一個分區,另外 2 個「完全閒置」

想提高並行度 → 必須增加分區數
⚠️ 但分區數「只能加不能減」,而且加分區會改變 key → partition 的對應
   → ★ 同一個 key 的新訊息可能落到不同分區,破壞既有的順序保證

★ 所以 Kafka 的分區數是一個要提前想清楚的容量決定,不像 RabbitMQ 可以隨時加 consumer。
實務做法:一開始就開比預期多的分區(例如預估並行度的 2–3 倍),空著不用的成本很低。

★★ 順序 vs 並行:這是點對點通道無法迴避的取捨

一個消費者   → ★ 嚴格順序,但吞吐 = 單一消費者的處理速度
多個消費者   → ★ 高吞吐,但同一通道的訊息會被並行處理 ⇒ 順序消失
// ★ 務實的折衷:只保證「同一個實體」的順序
//   用實體 ID 當分區鍵 → 同一張訂單的事件必然進同一個分區 → 必然被同一個消費者依序處理
//   不同訂單之間可以完全並行
producer.Send(ctx, Message{Key: []byte(order.ID), Value: body})

★ 更穩健的做法:讓處理與順序無關。
訊息帶版本號/OccurredAt,消費端丟棄比已處理狀態更舊的訊息
——這樣即使亂序也不會出錯,而且可以完全並行(見 訊息序列、工具-資料分區)。

★ 兩個容易被忽略的實務細節

① ★ RabbitMQ 的 prefetch(QoS)預設是「不限制」
   → 一個消費者可能把整個佇列抓進自己的記憶體,其他消費者拿不到訊息
   → ★ 一定要設 basic.qos(prefetch_count)。實務起點:等於併發 worker 數

② ★ SQS 的 visibility timeout 決定「重送」的時機
   預設 30 秒(查證 2026-09-25,最大 12 小時)
   → 處理超過 30 秒 → 訊息對別人再次可見 → ★ 同一則被兩個消費者處理

★ 兄弟辨析

差別
發布訂閱通道★ 每個訂閱者都拿到一份;點對點是「只有一個人拿到」
競爭消費者★ 這是點對點通道的消費端模式(多個消費者搶同一個通道)
訊息分派器一個消費者收下、在行程內分給多個 worker(★ 不是多個消費者)
選擇性消費者消費者只挑符合條件的(★ 會讓「一則只給一人」的語義變複雜)

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 若你要「同一則訊息讓多方都收到」(事件廣播),這模式不適用,該用發布訂閱通道。

  • 多個競爭消費者時,訊息的處理順序通常無法保證。

  • ★★ Kafka 的並行度上限是分區數。多出來的消費者完全閒置,而分區數只能加不能減。

  • ★ 加 Kafka 分區會改變 key → partition 的對應,破壞既有的順序保證。分區數要提前想清楚。

  • ★★ 多個消費者 ⇒ 順序消失。要順序就只能一個消費者,或用實體 ID 當分區鍵限縮順序範圍。

  • ★ RabbitMQ 的 prefetch 預設不限制,一個消費者會把佇列抓光。一定要設 basic.qos。

  • ★ SQS 標準佇列不保證順序也可能重複。需要順序要用 FIFO 佇列(有吞吐上限)。

  • ★ 處理時間超過 visibility timeout(SQS 預設 30 秒)會造成重複處理。

  • 更穩健的做法是「讓處理與順序無關」:帶版本號,丟棄過舊的訊息。

🔗 相關工具