🎯 什麼情境該想到我
當你「要把含多項目的訊息拆開、各別(可能分流到不同處理)處理、再彙整成結果」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:在維持整體訊息流一致的前提下,處理「含多個項目、各需不同處理」的訊息。
做法要點——組合三個模式:
- 分割器:把訊息拆成各個項目。
- 路由:把每個項目送到對應的處理。
- 聚合器:把各項處理結果彙整回一則訊息。
適合像「訂單各品項要向不同供應商查詢,再合併結果」這種情境。
★ 它就是 分割器 + N 個處理 + 聚合器
訂單 ──[分割器]──▶ 品項1 ──▶ 供應商A 詢價 ──┐
品項2 ──▶ 供應商B 詢價 ──┤──[聚合器]──▶ 完整報價
品項3 ──▶ 供應商A 詢價 ──┘
↑ ★ 各品項可能走不同的處理路徑
★ 和 散發-聚集 的差別(這兩個最常被搞混):
★ 組合訊息處理器:一則訊息「拆開」,★ 每個部分做「不同的」事
★ 散發-聚集: 一則訊息「複製」,★ 每個接收者做「同樣的」事(比價、投票)
★★ 這個模式的複雜度是三個模式相乘,不是相加
★ 你同時要處理:
① 分割器的原子性(發一半崩潰)+ 決定性 MessageID
② N 條處理路徑各自的失敗、重試、DLQ
③ ★★ 聚合器的完成條件、逾時、狀態持久化、併發原子性
④ ★★ 以及「某一個部分永遠失敗」時整體怎麼辦 ← ★ 這是新增的、最難的一個
★★ 第 ④ 點是組合訊息處理器獨有的難題:
品項 2 的供應商掛了三天 → 這張訂單的報價要等三天?還是輸出不完整的報價?
還是整張作廢?
★ 這是業務決定,不是技術決定,而且必須在寫程式之前就決定。
★★ 務實的替代方案:用資料庫當協調者
-- ★ 一張表取代「分割器 + 聚合器」的全部狀態管理
CREATE TABLE quote_parts (
quote_id text NOT NULL,
position int NOT NULL,
sku text NOT NULL,
status text NOT NULL, -- pending / done / failed
result jsonb,
updated_at timestamptz NOT NULL,
PRIMARY KEY (quote_id, position) -- ★ 唯一鍵天然提供冪等
);// ★ 每個部分處理完就 UPSERT 一列,然後檢查是否全部完成
// ★★ 併發安全由資料庫負責 —— 不要自己寫分散式狀態機
tx.Exec(ctx, `UPDATE quote_parts SET status='done', result=$1 WHERE quote_id=$2 AND position=$3`, …)
var pending int
tx.QueryRow(ctx, `SELECT count(*) FROM quote_parts WHERE quote_id=$1 AND status='pending'`, id).Scan(&pending)
if pending == 0 { emitAggregate(ctx, id) } // ★ 在同一個交易裡判斷★★ 這是實務上最划算的做法:
狀態、併發、持久化、冪等、逾時查詢(WHERE updated_at < now() - interval '10 min')
全部交給資料庫。你只需要一個掃逾時的 cron。
→ 見 聚合器 的「能不要用聚合器嗎」。
★ 什麼時候真的需要這個模式
- ★ 各部分的處理真的不同(不同供應商、不同演算法、不同外部系統)。
- ★ 各部分可以真正並行,而且並行帶來的收益大於協調成本。
- 需要「部分結果也有價值」(三家報價有兩家回來就能出價)。
★ 什麼時候不要
- ★ 各部分做同樣的事 → 那是 散發-聚集,簡單得多。
- ★ 各部分之間有相依(第二項要用第一項的結果)→ 那是 流程管理器。
- ★ 處理很快、項目很少 → 直接在一個函式裡迴圈處理,
用errgroup並行就好——不需要任何訊息基礎設施。 - 需要整體原子性 → 這個模式做不到。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
本質是複合模式,複雜度較高。
-
需管理關聯與完成條件(同聚合器)。
-
各項處理失敗的容錯要設計。
-
★★ 複雜度是三個模式相乘:分割器的原子性 + N 條路徑的失敗處理 + 聚合器的狀態管理。
-
★★ 「某一部分永遠失敗時整體怎麼辦」是業務決定(等待/輸出不完整/整批作廢),必須在寫程式前決定。
-
★★ 務實解是「用資料庫當協調者」:一張
(id, position)主鍵的表,狀態/併發/冪等/逾時查詢全交給資料庫。 -
★ 各部分做同樣的事 → 那是 散發-聚集,簡單得多。
-
★ 各部分有相依 → 那是 流程管理器,不是這個模式。
-
★ 處理快、項目少 → 一個函式加
errgroup就好,不需要訊息基礎設施。 -
這個模式做不到整體原子性。 需要的話要改用交易或 Saga。
-
分割出的子訊息 MessageID 必須是決定性的,否則重送時去重失效(見 分割器)。
🔗 相關工具
- 分割器 Splitter(組成元件之一)
- 聚合器 Aggregator(組成元件之一)
- 散發-聚集 Scatter-Gather(相似,但那是同一請求問多方)
- 企業整合模式
- 分割器、聚合器 —— 這個模式的兩端
- 散發-聚集 —— ★ 最容易混淆:各部分做同樣的事
- 流程管理器 —— 各部分有相依時
- 訊息序列 —— 子訊息的 position/total
- 工具-交易與隔離等級 —— 用資料庫做協調