🎯 什麼情境該想到我
當你「要把一個請求發給多個接收者,再把各自的回覆彙整成一個回應」(如比價)的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:把訊息送給多個收件者,並把它們的回覆重新彙整成單一訊息。
做法要點:
- 散發:用收件人清單(指定收件者)或 Publish-Subscribe(廣播)把請求送給多方。
- 聚集:用聚合器依完成條件與策略,把各方回覆合併(例如挑最低報價)。
★ 兩種散發方式,決定了整個模式的耦合度
① ★ 用 [[收件人清單]] 散發(我知道要問誰)
→ 適合「詢價要問這三家」——清單由業務規則決定
→ ★ 知道「應該收到幾個回覆」⇒ 完成條件明確
② ★ 用 [[發布訂閱通道]] 散發(誰想回誰回)
→ 適合「開放競標」——不知道有幾家會回
→ ★★ 完成條件只能靠逾時或「收到 N 個就夠了」
★★ 這個選擇決定了完成條件的難度,而完成條件是聚合器最難的部分(見 聚合器)。
② 幾乎必然要靠逾時,因為你不知道總數。
★★ 最重要的設計決定:不要等所有人
// ★ 三種完成策略,按實用度排序
type CompletionPolicy struct {
MinResponses int // ★★ 「收到 3 個就夠了」—— 最實用
Timeout time.Duration // ★ 永遠要有
RequireAll bool // ★ 慎用:一個慢的參與者拖垮整個流程
}★★
RequireAll是最常見的設計錯誤:
五個參與者,其中一個的 p99 是 30 秒 →
你的整個流程的 p99 就是 30 秒(而且那還是「它沒掛」的情況)。★ 這就是「可用性相乘」在非同步世界的版本:
五個 99.9% 的參與者,全部都要 →0.999⁵ = 99.5%(見 遠端程序呼叫)。
而「收到 3 個就夠」的可用性遠高於 99.9%。
★ 實務準則:能接受部分回覆就接受。 比價用三家報價中最好的,
而不是「非得五家都回」。
★ Go 的實作:行程內版本先考慮
// ★ 如果參與者是同步 API,行程內版本簡單得多 —— 不需要任何訊息基礎設施
func scatterGather(ctx context.Context, suppliers []Supplier, req Request) []Quote {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second) // ★ 整體逾時
defer cancel()
results := make(chan Quote, len(suppliers))
var wg sync.WaitGroup
for _, s := range suppliers {
wg.Add(1)
go func(s Supplier) { // ★ Go 1.22+ 迴圈變數已是每輪新建
defer wg.Done()
q, err := s.Quote(ctx, req) // ★ 慢的參與者被 ctx 砍掉
if err != nil { return } // ★ 失敗就不算,不中斷其他人
results <- q
}(s)
}
wg.Wait(); close(results)
var out []Quote
for q := range results { out = append(out, q) }
return out // ★ 可能是部分結果,呼叫端要能接受
}★★ 判準:參與者能同步回應嗎?
能 → 用上面這段,不要用訊息。errgroup + context 已經給你逾時、取消、並行。
不能(要幾分鐘、參與者可能離線)→ 才需要訊息版的散發-聚集。
★ 訊息版的三個額外負擔
① ★★ 回覆的關聯:每個參與者的回覆要帶 correlation id 回來
→ ★ 而且要能分辨「這是誰的回覆」(參與者身分也要帶)
→ 惡意/錯誤的參與者可能回覆別人的 correlation id ⇒ ★ 要驗證它在清單內
② ★★ 遲到的回覆
逾時後才到的回覆要怎麼辦?
→ ★ 丟棄(但要計數,否則你不知道逾時設太短)
→ ★★ 絕對不要「補上去重新聚合」—— 下游已經收到結果了
③ ★ 參與者自己掛了
它永遠不回 → 只有逾時能發現
→ ★ 要統計「每個參與者的回覆率」,否則你不知道有人長期沒在回
★ 第③點常被忽略但很致命:一個參與者靜默失效三個月,
流程每次都靠逾時完成、每次都少一家報價,而沒有任何告警。
★ 兄弟辨析
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
需決定散發方式,以及聚集的完成條件與逾時(不必等齊所有回覆)。
-
各接收者可能不回或很慢,要有容錯。
-
與組合訊息處理器相似,但這裡是「同一請求問多方」而非「拆項目」。
-
★★
RequireAll是最常見的設計錯誤。一個慢的參與者決定整個流程的 p99;五個 99.9% 全都要 = 99.5%。 -
★★ 能接受部分回覆就接受(「收到 3 個就夠」)。可用性遠高於「非得全部」。
-
★★ 參與者能同步回應 → 用
errgroup+context,不要用訊息基礎設施。 -
★ 遲到的回覆要丟棄並計數(計數讓你知道逾時設太短),絕對不要補上去重新聚合。
-
★ 要驗證回覆來自清單內的參與者,否則惡意/錯誤的回覆可以污染聚合結果。
-
★★ 要統計每個參與者的回覆率。一個參與者靜默失效三個月、每次都靠逾時完成,是不會告警的。
-
永遠要有整體逾時。完成條件靠「收齊」時,缺一個就永遠不完成。
-
呼叫端必須能接受部分結果,這是契約的一部分。
🔗 相關工具
- 收件人清單 Recipient List(散發端的一種做法)
- 聚合器 Aggregator(聚集端,彙整多方回覆)
- 組合訊息處理器 Composed Message Processor(相似的拆分-彙整結構)
- 企業整合模式
- 收件人清單、發布訂閱通道 —— 兩種散發方式
- 聚合器 —— 收集端
- 組合訊息處理器 —— ★ 各部分做不同的事
- 請求-回覆 —— 一對一版本
- 流程管理器 —— 多回合、可分支
- 遠端程序呼叫 —— 可用性相乘