🎯 什麼情境該想到我
當你「非同步的請求與回覆需要一個 ID 把它們配對起來」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:讓請求者能把收到的回覆,對應回自己送出的那一筆請求。
做法要點:
- 每個請求帶一個唯一的請求 ID。
- 回覆訊息在標頭放一個 Correlation Identifier,其值等於原始請求的 ID。
- 請求者收到回覆時,用這個關聯識別碼比對,找出對應的原始請求與其狀態。
★★ 三種 ID,三個不同用途,經常被混成一個
| ID | 用途 | 誰產生 | ★ 生命週期 |
|---|---|---|---|
message-id | ★ 冪等去重(見 冪等接收者) | 生產者,每則訊息都不同 | 一則訊息 |
correlation-id | ★ 串起一次業務流程(請求↔回覆、一次下單的所有事件) | 流程的起點,整條鏈共用同一個 | 整個業務流程 |
trace-id(W3C traceparent) | ★ 可觀測性:在 APM 裡看到完整 span 樹 | 入口(gateway/handler) | 一次分散式追蹤 |
★★ 最常見的錯誤是把
message-id當correlation-id用:
重送同一則訊息時message-id相同(對),但衍生的下游訊息會各有自己的 id——
於是你無法把「一次下單產生的十則訊息」串起來。★ 規則:
correlation-id只在流程起點產生一次,之後所有衍生訊息都原封不動地複製它。
type Envelope struct {
MessageID string `json:"message_id"` // ★ 每則都不同(uuid)
CorrelationID string `json:"correlation_id"` // ★★ 整條鏈相同
CausationID string `json:"causation_id"` // ★ 「我是被哪一則訊息引起的」
TraceParent string `json:"traceparent"` // ★ W3C 格式,給 APM 用
}★ causation-id 常被忽略,但它是唯一能重建「因果樹」的欄位:
correlation-id 告訴你「這些訊息屬於同一次流程」,
causation-id 告訴你「A 引起了 B,B 引起了 C」——出事時這才是你要的。
下單 API ──▶ OrderPlaced corr=X msg=1 cause=(none)
├──▶ ReserveStock corr=X msg=2 cause=1
│ └──▶ StockReserved corr=X msg=4 cause=2
└──▶ ChargeCard corr=X msg=3 cause=1
└──▶ ChargeFailed corr=X msg=5 cause=3
★ 用 corr=X 撈出全部;用 cause 鏈重建這棵樹 → 一眼看出是 ChargeCard 那條斷了
★★ 最重要的一件事:這件事必須在「第一天」就做
★ 事後補 correlation id 極貴:
① 每個服務的每個 publish 都要改(★ 而且漏一個就斷鏈)
② 已經在通道裡的歷史訊息沒有這個欄位,混合期間查詢邏輯要處理兩種
③ 日誌裡沒有 → 過去的事故永遠查不出來
★ 對策:把它做進共用的 client library,讓開發者「不必想起」它
(見 訊息匯流排):
// ★ 共用 library 自動傳遞 —— 開發者只要用 ctx,不必碰欄位
func Publish(ctx context.Context, topic string, msg any) error {
e := Envelope{
MessageID: uuid.NewString(),
CorrelationID: CorrelationFromContext(ctx), // ★ 沒有就產生一個
CausationID: CausationFromContext(ctx), // ★ 當前正在處理的 message id
TraceParent: traceParentFrom(ctx),
}
…
}
// ★ 消費端:把收到的訊息 id 放進 ctx,作為下游的 causation
func withMessageContext(ctx context.Context, e Envelope) context.Context {
ctx = WithCorrelation(ctx, e.CorrelationID)
ctx = WithCausation(ctx, e.MessageID) // ★★ 關鍵的一行
return ctx
}★ 和日誌/追蹤打通
// ★ 每一行日誌都帶 correlation id —— 這是它最大的價值
logger := slog.With("correlation_id", corr, "message_id", e.MessageID)★ W3C traceparent 是跨系統追蹤的標準格式,OpenTelemetry 直接認得。
不要自己發明格式——用 traceparent 就能讓訊息鏈出現在 APM 的 span 樹裡
(見 工具-遙測與監控)。
⚠️ 但 trace-id 不能取代 correlation-id:
trace 通常有取樣(只留 1%)和保留期(幾天);
業務流程的關聯需要 100% 保留、而且要能查幾個月前的。→ 兩者都要。
★ 兄弟辨析
| 差別 | |
|---|---|
| 請求-回覆、回覆位址 | ★ 這個 ID 是「對應哪個請求」那一半 |
| 訊息歷史 | ★ 記錄「經過了哪些元件」;關聯識別碼只是一個 ID |
| 冪等接收者 | ★ 用的是 message-id,不是 correlation-id |
| 聚合器 | ★ 用 correlation id 判斷「哪些訊息屬於同一組」 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
ID 必須夠唯一,避免配錯。
-
請求者需保存「待回覆請求」的狀態以供比對。
-
常與回覆位址一起,構成完整的非同步問答。
-
★★
message-id(去重)、correlation-id(串流程)、trace-id(可觀測性)是三件事,不要混成一個。 -
★★
correlation-id只在流程起點產生一次,之後所有衍生訊息原封不動複製。 -
★
causation-id才能重建因果樹。correlation-id只告訴你「屬於同一次流程」。 -
★★ 這件事必須第一天就做。事後補要改每個 publish,漏一個就斷鏈,而且過去的事故永遠查不出來。
-
★ 做進共用 client library,讓開發者不必想起它。
-
★
trace-id不能取代correlation-id:trace 有取樣(1%)和短保留期,業務關聯需要 100% 且長期保留。 -
用 W3C
traceparent標準格式,不要自己發明——OpenTelemetry 直接認得。 -
每一行日誌都要帶 correlation id,否則它只存在於訊息裡,查事故時用不到。
🔗 相關工具
- 請求-回覆 Request-Reply(關聯識別碼讓非同步的來回能配對)
- 回覆位址 Return Address(指定回哪,關聯識別碼負責配對)
- 訊息序列 Message Sequence(同樣靠標頭的識別資訊做關聯)
- 企業整合模式
- 請求-回覆、回覆位址 —— 請求-回覆的另一半
- 訊息歷史 —— 記錄經過的元件
- 冪等接收者 —— 用的是 message-id
- 聚合器 —— 用它判斷訊息分組
- 訊息匯流排 —— 做進共用 library
- 工具-遙測與監控 —— 與 APM 打通