先定義要回答的問題,再決定埋點範圍
導入分散式追蹤時,工程團隊很容易先從「每個服務都裝 SDK」開始,最後卻得到大量 span,仍然無法解釋使用者為何等待。更實際的起點,是先列出必須回答的維運問題:請求在哪一段排隊、哪個外部 API 最慢、訊息是否重試、消費者收到訊息後多久才開始處理,以及一次業務操作究竟跨過哪些同步與非同步元件。這些問題會決定 span 的邊界與必要屬性。
同步 HTTP 或 gRPC 流程通常可以沿用父子 span:入口服務建立或接續 trace,呼叫下游時注入 trace context,下游再建立子 span。資料庫查詢、外部 API 與重要的商業邏輯可以成為獨立 span,但不要替每個小函式埋點。span 太細會提高成本、增加視覺雜訊,也可能讓真正的等待時間被大量細節淹沒。判斷原則是:這一段是否有獨立的失敗模式、資源等待或維運責任人;若答案都是沒有,通常不需要單獨追蹤。
跨越佇列時,保留因果關係而非硬套同步模型
HTTP 呼叫的上下游關係相對直接,訊息佇列則不同。生產者送出訊息後,可能隔一段時間才由消費者處理;一則訊息也可能被重送、分派給多個消費者,或進入死信佇列。因此,應把「發佈」「在 broker 等待」「接收與處理」視為不同階段。生產者將標準 trace context 寫入訊息標頭,消費者取出後建立處理 span,並記錄訊息系統、topic 或 queue、操作類型、訊息識別碼與重送狀態。
是否讓消費者 span 直接成為生產者 span 的子節點,要看拓樸。單一生產者對單一消費者、且訊息生命週期短時,父子關係容易閱讀;若是 fan-out、批次消費或長時間延遲工作,使用 span link 通常更符合真實因果關係,也能避免產生跨越數小時的巨大 trace。批次一次包含多則訊息時,不應任意選一則作為父節點;可以建立批次處理 span,再連結各來源 trace,並只為需要獨立診斷的訊息建立詳細子 span。
- 傳播格式:優先採用 W3C Trace Context,並確認 API gateway、服務框架、broker client 與背景工作框架不會移除或改寫標頭。
- 排隊時間:記錄訊息發佈時間或 broker 可提供的 enqueue 時間,讓系統能區分「等待被消費」與「消費者執行很慢」。
- 重試與重送:保留原始業務識別碼,但為每次處理建立新的 span,標示 attempt、redelivery 與失敗原因,避免把多次執行誤認為一次超長操作。
- 敏感資料:不要把完整 payload、存取權杖、客戶資料或 SQL 參數放入 span;以受控的類別、雜湊或內部識別碼協助關聯。
讓取樣、屬性與錯誤狀態支援實際排查
全量收集在測試環境很方便,但正式環境通常需要考量流量、儲存成本與查詢效能。單純固定比例取樣可能剛好丟掉低頻錯誤或極慢請求,因此可將 head sampling 與後端 tail sampling 搭配:入口先保留可接受的基礎比例,後端再優先留下錯誤、逾時、高延遲或特定重要流程。必須確認整條 trace 使用一致的取樣決策,否則畫面會出現看似缺少中間服務的斷裂鏈路。
屬性設計比 span 數量更重要。建議建立跨團隊共用的低基數欄位,例如 service、environment、operation、dependency、queue、message type、result 與 retry reason。租戶、訂單或裝置識別碼若有診斷價值,應先評估基數、隱私及保留期限,必要時只在錯誤事件或受控日誌中保存。錯誤狀態也要一致:業務拒絕不一定是系統錯誤,捕捉到例外後成功補償也不應讓根 span 永遠顯示失敗;狀態應反映呼叫者最後看到的結果,同時用事件保留中間異常。
用固定排查流程驗證追蹤是否真的可用
遇到延遲時,先從入口 span 確認端到端耗時與臨界路徑,再比較服務執行時間、下游呼叫時間及佇列等待時間。若 API 很快但訊息晚到,應檢查 consumer lag、併發限制、分割區分配與下游節流;若訊息很快被取出但處理 span 很長,則查看資料庫鎖、外部依賴、連線池與重試。若同一段反覆出現,還要確認是應用程式重試、broker 重送,還是工作完成但 acknowledgement 失敗。
上線前應以可控制的測試案例驗證整條鏈路:正常請求、下游逾時、訊息延遲、消費者失敗後重試、死信處理及 fan-out。除了檢查 trace 是否存在,也要確認服務名稱穩定、時間軸合理、錯誤落在正確 span,且能從告警或日誌快速跳到同一個 trace。最後再把追蹤與指標、結構化日誌串接;trace 適合解釋單次請求,指標適合判斷影響範圍,日誌則補充細節。三者分工清楚,分散式追蹤才會成為日常維運工具,而不是只有事故發生時才想起來的儀表板。