🎯 什麼情境該想到我
當你「想讓多個系統以非同步、可靠、鬆耦合的方式交換資料,且不想因對方暫時離線就失敗」的時候。這是本書其餘所有模式的基礎風格。
⚙️ 怎麼用(步驟 / 公式)
意圖:用訊息系統在應用程式間頻繁、即時、可靠且非同步地傳輸資料封包(訊息)。
做法 / 結構要點:
- 發送方把資料包成訊息,交給訊息系統送到通道上,不必等接收方立即處理。
- 訊息系統負責投遞、緩衝與重試,發送與接收在時間上解耦。
- 因為非同步且有緩衝,能吸收流量尖峰(削峰)、容忍對方暫時離線,也較易擴充。
★ 這個模式的四個具體承諾,以及各自的代價
| 承諾 | 怎麼達成 | ★ 代價 |
|---|---|---|
| 非同步 | 發送者不等回應 | ★ 錯誤回饋變成另一個問題:使用者已經拿到「成功」了 |
| 解耦 | 雙方只認識通道 | ★ 因果鏈變得看不見——「為什麼這筆沒出貨」要追過五個佇列 |
| 可靠 | 持久化 + 重送 | ★ 重送 ⇒ 重複 ⇒ 消費端必須冪等 |
| 削峰 | 佇列吸收突發 | ★ 積壓時延遲持續惡化,而且不會自己好 |
★★ 訊息傳遞不是「比較好的 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 當任務佇列。它不支援「單筆稍後再試而其他繼續」的語義。
-
★ 積壓不會自己好。消費者跟不上時延遲會持續惡化,一定要告警。
-
★ 非同步讓錯誤回饋變成產品問題:使用者已經看到「成功」了,後續失敗怎麼通知?
-
不要把 MQ 當資料庫。要查歷史就寫進資料庫。
-
MQ 是要維運的基礎設施。兩個服務、低流量 → 直接呼叫更好。
🔗 相關工具
- 遠端程序呼叫 Remote Procedure Invocation(同為整合風格,對照的同步做法)
- 訊息通道 Message Channel(訊息傳遞的基礎:訊息流經的管道)
- 訊息 Message(訊息傳遞的基礎:傳輸的資料封包)
- 企業整合模式
- 檔案傳輸、共享資料庫、遠端程序呼叫 —— 另外三種整合風格
- 工具-訊息傳遞整合模式 —— ★ 上位決策:命令 vs 事件、投遞保證、順序、DLQ
- 工具-MQ消費端防禦三原則 —— 消費端的冪等、重試、背壓怎麼寫
- 訊息通道、訊息、訊息端點 —— 訊息系統的根模式
- 保證投遞、冪等接收者、死信通道 —— 可靠性三件套
- 工具-Transactional-Outbox-Pattern —— 資料庫狀態與訊息不脫節
- 工具-select超時與競速 —— Go 消費端的優雅關閉