🎯 什麼情境該想到我

當你「想讓多個系統以非同步、可靠、鬆耦合的方式交換資料,且不想因對方暫時離線就失敗」的時候。這是本書其餘所有模式的基礎風格。

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

意圖:用訊息系統在應用程式間頻繁、即時、可靠且非同步地傳輸資料封包(訊息)。

做法 / 結構要點:

  • 發送方把資料包成訊息,交給訊息系統送到通道上,不必等接收方立即處理。
  • 訊息系統負責投遞、緩衝與重試,發送與接收在時間上解耦。
  • 因為非同步且有緩衝,能吸收流量尖峰(削峰)、容忍對方暫時離線,也較易擴充。

★ 這個模式的四個具體承諾,以及各自的代價

承諾怎麼達成★ 代價
非同步發送者不等回應★ 錯誤回饋變成另一個問題:使用者已經拿到「成功」了
解耦雙方只認識通道★ 因果鏈變得看不見——「為什麼這筆沒出貨」要追過五個佇列
可靠持久化 + 重送★ 重送 ⇒ 重複 ⇒ 消費端必須冪等
削峰佇列吸收突發★ 積壓時延遲持續惡化,而且不會自己好

★★ 訊息傳遞不是「比較好的 RPC」,它是把一組問題換成另一組問題。
選它是因為你寧願處理重複與亂序,也不想處理「對方現在必須活著」。

★ 2026 年的選型(EIP 寫於 2003 年的 JMS 時代)

模型最適合★ 要注意
Kafka★ 持久化的分區日誌(訊息讀完不消失)事件流、重放、高吞吐、多消費者各自進度★ 並行度被分區數限制;沒有原生 DLQ;不適合每則訊息各自 ack
RabbitMQ佇列 + exchange 路由複雜路由、任務佇列、per-message ack★ 佇列太長會變慢;路由設定容易變成沒人懂的拓撲
SQS/SNS託管佇列/主題★ 不想維運、流量不穩★ 標準佇列不保證順序;FIFO 有吞吐上限
NATS / JetStream輕量 pub/sub(+ 持久化)低延遲、服務間通知、邊緣生態較小
Redis Streams日誌 + consumer group已經有 Redis、量不大★ 持久性取決於 Redis 的持久化設定

★ 最常見的選型錯誤:把 Kafka 當任務佇列。
Kafka 的消費模型是「一個分區一個消費者、按 offset 前進」——
你不能讓某一則訊息「稍後再試」而其他訊息繼續前進。
要那個語義(重試單筆、per-message ack、DLQ)→ RabbitMQ/SQS 更合適。

★ Go 的實作面

// ★ 消費端的骨架:ctx 控制關閉、確認在處理成功「之後」
for {
    msg, err := consumer.Fetch(ctx)
    if err != nil {
        if errors.Is(err, context.Canceled) { return nil }   // ★ 優雅關閉
        return fmt.Errorf("fetch: %w", err)
    }
    if err := handle(ctx, msg); err != nil {
        // ★ 不要 ack —— 讓它重送(或明確 nack 進 DLQ)
        continue
    }
    if err := msg.Ack(); err != nil { … }   // ★★ ack 在處理成功之後
}

⚠️ 「先 ack 再處理」是最常見也最嚴重的錯誤——處理失敗時訊息已經沒了。
(Go 端的優雅關閉與 select 超時見 工具-select超時與競速。)

★ 開始用訊息之前要先回答的四個問題

① 投遞保證要哪一種?    → ★ 實務上只有「至少一次」,所以消費端必須冪等
② 順序需要到什麼粒度?  → ★ 幾乎都只需要「同一個實體有序」,用實體 ID 當分區鍵
③ 失敗的訊息去哪裡?    → ★ DLQ,而且要有告警與重放流程
④ 訊息的 schema 誰擁有?→ ★ 生產者擁有;消費者可能是舊版,要向後相容

★ 這四題答不出來就先不要引入 MQ。 完整討論見 工具-訊息傳遞整合模式(上位決策)
與 工具-MQ消費端防禦三原則(消費端寫法)。

★ 什麼時候不要用訊息

  • 需要立即回應(查詢、驗證)→ 遠端程序呼叫。
  • ★ 只有兩個服務、量也不大 → 直接呼叫。MQ 是一整套要維運、要監控、要值班的基礎設施。
  • 需要跨實體的強一致 → 同一個資料庫的交易(見 共享資料庫)。
  • ★ 只是想「解耦」但沒有實際的非同步需求 → 先問解耦要解決什麼問題。
  • 大量資料搬運 → 檔案傳輸 便宜一到兩個數量級。

🧪 我實際套用的紀錄

  • (待填)

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

  • 非同步帶來複雜度:需處理訊息順序、重複、最終一致性,除錯比同步呼叫困難。

  • 若真的需要「呼叫後立即拿到回覆」的即時語意,同步 RPC 反而更直接。

  • 引入訊息中介系統本身也需維運成本。

  • ★★ 訊息傳遞不是「比較好的 RPC」,是把一組問題換成另一組(重複、亂序、看不見的因果鏈)。

  • ★★ 「先 ack 再處理」是最嚴重的實作錯誤。ack 一定在處理成功之後。

  • ★ 實務上只有「至少一次」。「恰好一次」在有外部副作用時不存在 → 消費端必須冪等。

  • ★ 不要把 Kafka 當任務佇列。它不支援「單筆稍後再試而其他繼續」的語義。

  • ★ 積壓不會自己好。消費者跟不上時延遲會持續惡化,一定要告警。

  • ★ 非同步讓錯誤回饋變成產品問題:使用者已經看到「成功」了,後續失敗怎麼通知?

  • ★ 因果鏈變得看不見。訊息一定要帶 correlation/trace id(見 訊息歷史、工具-遙測與監控)。

  • 不要把 MQ 當資料庫。要查歷史就寫進資料庫。

  • MQ 是要維運的基礎設施。兩個服務、低流量 → 直接呼叫更好。

🔗 相關工具