🎯 什麼情境該想到我
當你要用訊息佇列/事件把多個系統串接起來,思考「訊息怎麼流、怎麼繞送、怎麼轉換」時。
⚙️ 怎麼用(選對模式組合)
- 通道(Channel):訊息的傳輸管道;點對點(一對一消費)vs 發布訂閱(一對多)。
- 路由(Routing):
- Router 依內容決定去向、Filter 過濾、Splitter 拆分、Aggregator 聚合多筆為一。
- 轉換(Transformation):Translator 轉格式、Enricher 補資料——讓不同系統的訊息互通。
- 端點(Endpoint):系統如何收發;冪等消費者處理重複投遞(見 工具-MQ消費端防禦三原則)。
- 可靠性:保證投遞 + 死信佇列 + 重試退避。
★ 先分清楚:命令 vs 事件 vs 文件訊息
| 類型 | 語義 | 接收者 | 可以拒絕嗎 | 命名 |
|---|---|---|---|---|
| 命令 Command | 「請做這件事」 | ★ 恰好一個 | ✅ | 祈使句:CreateOrder |
| ★ 事件 Event | 「這件事已經發生了」 | ★ 零到多個 | ❌ 已經發生了 | 過去式:OrderPlaced |
| 文件 Document | 「這是資料」 | 任意 | — | 名詞 |
★★ 混用這三者的語義,是事件驅動架構最常見也最致命的設計錯誤。
把SendEmail(命令)發成事件 → 兩個服務都訂閱了 → 寄了兩封信。
把OrderPlaced(事件)當命令 → 發布者開始關心「誰處理了、處理成功沒」→ 耦合回來了。判準:發布者在不在乎「有沒有人處理」? 在乎 → 命令;不在乎 → 事件。
★ 投遞保證:三種語義
| 保證 | 意思 | 實務 |
|---|---|---|
| 至多一次 | 可能丟失 | 遙測、指標 |
| ★ 至少一次 | 可能重複 | ★ 幾乎所有 MQ 的實際保證 |
| 恰好一次 | 不丟不重 | ★ 端到端幾乎不存在 |
★★ 「恰好一次」在分散式系統裡基本上是行銷用語。
Kafka 的 exactly-once 只在「Kafka → 處理 → Kafka」的封閉迴圈內成立;
一旦你的處理有外部副作用(呼叫 API、寄信、寫別的資料庫),就回到「至少一次 + 冪等」。所以:接受「至少一次」,把冪等做好。 這是唯一務實的路線(見 工具-MQ消費端防禦三原則)。
冪等的三種做法:
① ★ 訊息帶唯一 ID,消費端記錄「處理過的 ID」(去重表 + 唯一索引)
② ★ 讓操作天然冪等:SET 而非 INCREMENT、UPSERT 而非 INSERT
③ 版本號/條件更新:WHERE version = ?(見 [[工具-交易與隔離等級]])
★ 順序保證:代價比想像中大
★ 要保證順序 → 同一個 key 的訊息必須進同一個分區,且該分區只能有一個消費者
→ ★ 那個 key 的處理就無法並行
→ 吞吐量被單一消費者限制
★ 判準:真的需要全域順序嗎? 多數情況只需要**「同一個實體的順序」**
(同一個訂單的事件要有序,不同訂單之間無所謂)→ 用實體 ID 當分區鍵(見 工具-資料分區)。
更好的做法:讓處理與順序無關
訊息帶版本號或時間戳,消費端丟棄比已處理的更舊的訊息。
★ 這樣即使亂序也不會出錯,而且可以並行處理。
★ 死信佇列(DLQ):一定要有,但更要有處理流程
訊息處理失敗 → 重試 N 次(指數退避)→ 仍失敗 → ★ 送進 DLQ
| 沒有 DLQ 會怎樣 | 有 DLQ 但沒流程會怎樣 |
|---|---|
| ★ 毒訊息(poison message)卡住整個佇列 —— 一直重試同一筆,後面全部堵住 | ★ DLQ 累積幾萬筆沒人看,等於資料默默遺失 |
★ DLQ 必須配三件事:
- 告警(有訊息進 DLQ 就通知)
- 可檢視(能看到訊息內容與失敗原因)
- ★ 可重放(修好之後能把它們重新送回主佇列)
★ 常用的路由模式(EIP 的核心幾個)
| 模式 | 做什麼 | 對應 |
|---|---|---|
| 內容路由器 | 依訊息內容決定去向 | 內容路由器 |
| 訊息過濾器 | 不符條件就丟棄 | 訊息過濾器 |
| 分割器 Splitter | 一則拆成多則(訂單 → 每個品項一則) | 分割器 |
| ★ 聚合器 Aggregator | 多則合成一則。★ 最難的一個:要決定「等多久」「等幾則」「逾時怎麼辦」 | 聚合器 |
| 散發-聚集 | 發給多個處理者再收集結果 | 散發-聚集 |
| ★ 收件人清單 | 發給一份動態決定的清單 | 收件人清單 |
★ 完整的 65 條型錄見 reference/企業整合模式/。
★ 什麼時候不該用訊息佇列
| 情況 | 用什麼 |
|---|---|
| 需要立即的回應 | 同步 HTTP/gRPC |
| ★ 只有兩個服務、呼叫量小 | 直接呼叫——MQ 是一整套要維運的基礎設施 |
| 需要強一致 | 交易(見 工具-分散式事務的取捨) |
| 只是想「解耦」但沒有實際的非同步需求 | ★ 先問解耦要解決什麼問題 |
🧪 我實際套用的紀錄
- 2026-07-15:(待填)
⚠️ 注意 / 什麼時候不適用
- ★★ 消費端冪等不是可選的。所有實務上的 MQ 都是「至少一次」。
- ★ 命令與事件的語義不要混用(見上)。這會在系統長大後變成難以拆解的耦合。
- ★ DLQ 要有處理流程,否則它只是「資料消失的地方」。
- 訊息佇列會隱藏因果鏈。「為什麼這筆訂單沒出貨」要追過好幾個佇列——
★ 訊息一定要帶 correlation/trace id(見 工具-遙測與監控)。 - ★ 佇列積壓要告警。積壓代表消費者跟不上——而且延遲會持續惡化直到消費者追上或佇列爆掉。
- 訊息的 schema 也要版本管理。消費者可能是舊版(見 工具-資料編碼與演進)。
- 不要把 MQ 當資料庫。它是傳輸不是儲存;需要查詢歷史就寫進資料庫。
- ★ 非同步讓錯誤處理變難:呼叫端已經回應使用者了,後續失敗要怎麼通知?這要在產品層面設計
(見 工具-分散式事務的取捨 的產品配合)。
🔗 相關工具
- 工具-MQ消費端防禦三原則 —— ★ 冪等、重試、背壓的具體做法
- 工具-Transactional-Outbox-Pattern —— 可靠發布
- 工具-分散式事務的取捨 —— 最終一致的整體設計
- 工具-資料編碼與演進 —— 訊息 schema 的相容性
- 工具-資料分區 —— 分區鍵決定順序保證的粒度
- 命令模式 —— 命令的序列化與重放
- 觀察者模式 —— 行程內事件 vs 跨程序訊息
- 企業整合模式