🎯 什麼情境該想到我

當你「送出一則請求訊息,並在另一條通道等待對方回一則回覆」的時候。

⚙️ 怎麼用(步驟 / 公式)

意圖:當兩個應用要用訊息進行雙向對話(一來一回)。

做法要點:

  • 使用兩條通道:一條給請求、一條給回覆。
  • 請求者送出請求後,在回覆通道等待對方的回應。
  • 可同步(阻塞等回覆)或非同步(收到回覆再處理)。
  • 回覆通道常由回覆位址指定、回覆與請求用關聯識別碼配對。

★★ 先問一個問題:你真的需要用訊息做請求-回覆嗎?

用訊息實作 請求-回覆 = ★ 你在用非同步基礎設施重建一個同步呼叫
→ 而且通常會建出一個比 HTTP/gRPC 更差的版本:
  ★ 沒有現成的超時語義       ★ 沒有現成的錯誤碼
  ★ 沒有現成的重試策略       ★ 追蹤與除錯更難
  ★ 要自己管回覆通道的生命週期

★★ 判準:呼叫端會「阻塞等待」回覆嗎?
會 → 直接用 遠端程序呼叫(gRPC/HTTP),不要用訊息。
不會(呼叫端只是想在「稍後」知道結果)→ 才是這個模式的正確場景。

★ 這個模式真正合理的三種場景:

  1. ★ 處理要很久(幾分鐘到幾小時)——轉檔、產報表、批次計算。HTTP 撐不住。
  2. ★ 接收端可能離線——需要排隊與重試。
  3. ★ 已經有訊息基礎設施,而且要送的是命令(命令可以被拒絕 → 需要回覆路徑)。

★ 回覆通道的三種做法

做法說明★ 代價
① 固定的共用回覆通道所有回覆進同一條,用 關聯識別碼 分辨★ 要求所有請求者共用一個消費者,或每個都收下再丟棄
② ★ 每個請求者一條固定通道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,讓接收端知道「超過這個時間做了也沒用」,別浪費算力。

🔗 相關工具