🎯 什麼情境該想到我
當你「送出一則請求訊息,並在另一條通道等待對方回一則回覆」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:當兩個應用要用訊息進行雙向對話(一來一回)。
做法要點:
- 使用兩條通道:一條給請求、一條給回覆。
- 請求者送出請求後,在回覆通道等待對方的回應。
- 可同步(阻塞等回覆)或非同步(收到回覆再處理)。
- 回覆通道常由回覆位址指定、回覆與請求用關聯識別碼配對。
★★ 先問一個問題:你真的需要用訊息做請求-回覆嗎?
用訊息實作 請求-回覆 = ★ 你在用非同步基礎設施重建一個同步呼叫
→ 而且通常會建出一個比 HTTP/gRPC 更差的版本:
★ 沒有現成的超時語義 ★ 沒有現成的錯誤碼
★ 沒有現成的重試策略 ★ 追蹤與除錯更難
★ 要自己管回覆通道的生命週期
★★ 判準:呼叫端會「阻塞等待」回覆嗎?
會 → 直接用 遠端程序呼叫(gRPC/HTTP),不要用訊息。
不會(呼叫端只是想在「稍後」知道結果)→ 才是這個模式的正確場景。
★ 這個模式真正合理的三種場景:
- ★ 處理要很久(幾分鐘到幾小時)——轉檔、產報表、批次計算。HTTP 撐不住。
- ★ 接收端可能離線——需要排隊與重試。
- ★ 已經有訊息基礎設施,而且要送的是命令(命令可以被拒絕 → 需要回覆路徑)。
★ 回覆通道的三種做法
| 做法 | 說明 | ★ 代價 |
|---|---|---|
| ① 固定的共用回覆通道 | 所有回覆進同一條,用 關聯識別碼 分辨 | ★ 要求所有請求者共用一個消費者,或每個都收下再丟棄 |
| ② ★ 每個請求者一條固定通道 | svc-a.replies | ✅ 最實用;請求者重啟後回覆還在 |
| ③ 臨時/獨佔通道 | 每次請求建一條,用完刪 | ★ 建通道有成本;RabbitMQ 可行,Kafka 完全不適合(建 topic 很貴) |
// ★ ② 的實作要點:回覆位址寫在請求裡(見 [[回覆位址]])
type ConvertRequest struct {
RequestID string `json:"request_id"` // ★★ 關聯識別碼
ReplyTo string `json:"reply_to"` // ★ "svc-a.replies"
SourceKey string `json:"source_key"`
Deadline time.Time `json:"deadline"` // ★ 讓接收端知道「超過這個時間就別做了」
}⚠️ ReplyTo 由請求者填,但不可以盲目信任——
若請求來自外部,惡意的 ReplyTo 可以把你的回覆導到任意通道。要白名單驗證。
★★ 最大的難題:回覆可能永遠不來
// ★ 非同步請求-回覆必須把「等待中的請求」持久化,不能只放記憶體
type PendingRequest struct {
RequestID string
State string // pending / completed / timed_out
Deadline time.Time // ★ 由一個排程器掃過期的
CreatedAt time.Time
}為什麼不能只放記憶體:
★ 請求者重啟 → 記憶體裡的等待清單消失 → 回覆來了沒人認得(成為孤兒)
★ 回覆永遠不來 → 沒有任何機制會發現(沒有錯誤、沒有超時)
→ ★★ 必須有一個「掃過期請求」的排程器,把它們標成 timed_out 並決定怎麼處理
★★ 這是「用訊息做請求-回覆」最常被低估的工作量:
你必須自己實作 HTTP client 免費給你的超時機制,而且要做成持久化的。
★ 錯誤回覆的設計
// ★ 回覆必須能表達「失敗」,而且要分類
type ConvertReply struct {
RequestID string `json:"request_id"`
Status string `json:"status"` // ok / invalid / transient_error
ResultKey string `json:"result_key,omitempty"`
Error string `json:"error,omitempty"`
}⚠️ 常見錯誤:失敗時「什麼都不回」 →
請求者只能靠超時發現,而且分不出「失敗了」和「還在處理」。
★ 失敗也要回一則訊息。
★ 兄弟辨析
| 差別 | |
|---|---|
| 回覆位址 | ★ 「回覆該送去哪」——本模式的一半 |
| 關聯識別碼 | ★ 「這則回覆對應哪個請求」——本模式的另一半 |
| 遠端程序呼叫 | ★ 呼叫端要阻塞等待時的正確選擇 |
| 散發-聚集 | 一個請求送給多方、收集多個回覆 |
| 智慧代理 | ★ 攔在中間記錄請求與回覆的配對(用於監控) |
| 流程管理器 | 多回合的請求-回覆,且有狀態與分支 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
需管理回覆通道與逾時處理。
-
非同步下配對回覆須靠關聯識別碼。
-
同步阻塞等待會抵消訊息非同步的好處,量大時要慎用。
-
★★ 呼叫端會阻塞等待 → 直接用 gRPC/HTTP,不要用訊息重建一個更差的同步呼叫。
-
★★ 必須持久化「等待中的請求」。只放記憶體 → 重啟後回覆變孤兒,而且「回覆永遠不來」沒有任何機制會發現。
-
★ 必須有排程器掃過期請求。這是 HTTP client 免費給你、而訊息要自己做的部分。
-
★ 失敗也要回一則訊息。什麼都不回會讓請求者分不出「失敗」和「還在處理」。
-
★
ReplyTo不可盲目信任。外部來源的回覆位址要白名單驗證,否則可被導到任意通道。 -
★ Kafka 不適合臨時回覆通道(建 topic 很貴)。用「每個服務一條固定回覆通道」。
-
回覆也可能重複(至少一次投遞)。請求者處理回覆時同樣要冪等。
-
要帶 deadline,讓接收端知道「超過這個時間做了也沒用」,別浪費算力。
🔗 相關工具
- 回覆位址 Return Address(在請求裡指定回覆送往哪條通道)
- 關聯識別碼 Correlation Identifier(把回覆對應回原始請求)
- 命令訊息 Command Message(需要結果的命令常搭配請求-回覆)
- 企業整合模式
- 回覆位址、關聯識別碼 —— 這個模式的兩半
- 遠端程序呼叫 —— ★ 需要阻塞等待時的正確選擇
- 命令訊息 —— 命令可以被拒絕,所以需要回覆
- 散發-聚集 —— 一請求多回覆
- 流程管理器 —— 多回合且有狀態
- 智慧代理 —— 監控請求-回覆的配對