🎯 什麼情境該想到我
當你「發請求時要告訴接收方『回覆該送到哪一條通道』」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:讓回覆者知道要把回覆送往哪個通道,而不是寫死回哪。
做法要點:
- 在請求訊息的標頭帶上「回覆通道的位址」(Return Address)。
- 接收端讀取此位址,把回覆送往指定通道。
- 讓請求-回覆中不同請求者能各自指定專屬的回覆通道。
★★ 核心紀律:回覆位址由「請求者」決定,寫在請求裡
// ❌ 接收端寫死回覆去哪 —— ★ 加一個新的請求者就要改接收端
func handle(req Request) { publish(ctx, "svc-a.replies", reply) }
// ✅ 請求者說了算
type Request struct {
RequestID string `json:"request_id"`
ReplyTo string `json:"reply_to"` // ★ 由請求者填
}
func handle(req Request) { publish(ctx, req.ReplyTo, reply) }★ 這就是「回覆位址」的全部價值:讓接收端不必認識任何請求者。
同一個服務可以同時服務十個請求者,而它的程式碼裡沒有任何一個的名字。
★ 各家 broker 的原生支援
| broker | 機制 |
|---|---|
| AMQP/RabbitMQ | ★ reply-to 是 AMQP 協定的標準 property,不必自己定義 |
| JMS | JMSReplyTo(EIP 書寫的就是這個) |
| Kafka | ★ 沒有原生欄位——自己在 headers 放 reply-to |
| SQS | 沒有原生——放 message attribute |
| NATS | ★ 有內建的 request-reply(nc.Request() 自動處理回覆主題) |
★ NATS 是唯一把請求-回覆做成一級功能的:nc.Request(subj, data, timeout)
直接給你同步語義,回覆主題與關聯都由 client 處理掉。
★★ 安全:這是最容易被忽略的攻擊面
// ❌ 盲目信任請求裡的 ReplyTo
publish(ctx, req.ReplyTo, reply)
// ★★ 惡意請求者可以把 ReplyTo 設成任何通道:
// ① 導到別人的回覆通道 → ★ 污染他人資料
// ② 導到高流量通道 → ★ 用你的服務當放大器打別人(SSRF 的訊息版)
// ③ 導到不存在的通道 → RabbitMQ 靜默丟棄;Kafka 可能自動建 topic → ★ topic 爆增
// ✅ 白名單驗證
var allowedReplyChannels = map[string]bool{
"svc-a.replies": true,
"svc-b.replies": true,
}
if !allowedReplyChannels[req.ReplyTo] {
return publishTo(invalidChannel, req, errors.New("reply_to 不在白名單"))
}★ 或更穩健:不要讓請求者指定完整通道名,只讓它指定一個「識別碼」,
由接收端自己對應到通道名。
⚠️ Kafka 的
auto.create.topics.enable若為開啟,這個漏洞會直接造成 topic 爆增。
生產環境應該關掉它。
★ 回覆位址不見/無效時要怎麼辦
switch {
case req.ReplyTo == "":
// ★ 判斷:這個請求「本來就不需要回覆」,還是「請求者忘了填」?
// → 看訊息型別。命令通常需要回覆;fire-and-forget 不需要
// → ★ 曖昧的話就當成錯誤,送 [[無效訊息通道]],不要靜默不回
case !allowed(req.ReplyTo):
publishTo(invalidChannel, req, errInvalidReplyTo)
default:
publish(ctx, req.ReplyTo, reply) // ★ 這一步失敗也要處理:回覆發不出去 ≠ 工作沒做完
}★★ 最容易漏的一點:「工作做完了但回覆發不出去」。
如果你在這時 nack 訊息 → 整個工作會被重做(可能重複扣款)。
→ ★ 正確做法:工作與回覆分開記錄。 工作完成就記在資料庫,
回覆失敗單獨重試(這其實是 工具-Transactional-Outbox-Pattern 的同一個問題)。
★ 兄弟辨析
| 差別 | |
|---|---|
| 請求-回覆 | ★ 整體模式;回覆位址是它的「去哪」那一半 |
| 關聯識別碼 | ★ 「這則回覆對應哪個請求」——「哪一個」那一半 |
| 路由單 | ★ 訊息帶著整條路徑,不只是回覆位址 |
| 訊息閘道 | 請求者端封裝「發請求 + 等回覆」的地方 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
位址放在訊息標頭,而非本體。
-
需配合關聯識別碼,才能把送回來的回覆對應回原請求。
-
要確保指定的回覆通道存在、且請求者正在監聽。
-
★★ 不可盲目信任請求裡的
ReplyTo。可被用來污染他人通道、當放大器、或造成 topic 爆增。要白名單驗證。 -
★ Kafka 生產環境應關掉
auto.create.topics.enable,否則惡意的reply-to會製造大量 topic。 -
★★ 「工作做完但回覆發不出去」不可以 nack,否則整個工作會重做(可能重複扣款)。工作與回覆要分開記錄。
-
★
ReplyTo為空時不要靜默不回。判斷是「本來不需要」還是「忘了填」,曖昧就送無效訊息通道。 -
★ 回覆位址由請求者決定,接收端不該寫死——否則加一個請求者就要改接收端。
-
Kafka/SQS 沒有原生
reply-to欄位,要自己在 headers/attributes 定義並統一規範。 -
更穩健的做法是讓請求者只指定識別碼,由接收端對應到真正的通道名。
🔗 相關工具
- 請求-回覆 Request-Reply(回覆位址是其常用配套)
- 關聯識別碼 Correlation Identifier(配對回覆與請求)
- 企業整合模式
- 請求-回覆 —— 整體模式
- 關聯識別碼 —— 「對應哪個請求」那一半
- 路由單 —— 訊息帶著整條路徑
- 無效訊息通道 —— 回覆位址無效時的去處
- 訊息閘道 —— 請求者端的封裝
- 工具-Transactional-Outbox-Pattern —— 「做完了但發不出去」的同一個問題