題目與適用情境
設計一個供後端與可靠性團隊使用的多租戶分散式追蹤平台,用來追蹤一次請求或非同步工作流程 經過多個服務的完整路徑。面試假設為每秒新增 50 萬條根工作流程,每條工作流程平均 15 個 Span, 每個編碼後的 Span 在儲存壓縮前平均 700 bytes。保留的追蹤應在 30 秒內達到 p99 可搜尋;依 租戶與 Trace ID 精確查詢的 p99 不超過 2 秒;依服務、操作、錯誤、耗時與時間區間進行的常用 搜尋,p95 不超過 3 秒。埋點不能同步等待中央平台,失去一個可用區也不能阻止應用程式繼續上報。
平台負責建立或接收 Span、在支援的傳輸協定上依 W3C Trace Context 傳播上下文、組裝延遲或 亂序抵達的 Span、在明確預算內對整條追蹤取樣、儲存追蹤明細,並根據保留樣本產生服務相依圖。 為每種語言實作完整 SDK、完整 APM 介面、日誌平台、指標平台與異常偵測都不在範圍內。日誌可 攜帶 Trace ID,指標可攜帶 exemplar,但它們仍是獨立系統。
三份彼此獨立、發表於 2026 年的面試準備頁面,都把分散式追蹤列為系統設計題,並涵蓋 Span、 上下文傳播、追蹤組裝、取樣與查詢儲存。這足以證明題目在目前環境中的代表性,卻不能證明公司 歸屬,因此本文不作公司聲明。技術模型以 W3C Trace Context 推薦標準、OpenTelemetry 規格與 Collector 實作,以及 Google 的 Dapper 論文為依據。
面試官評估重點
第一個訊號是候選人能否跨程序保留因果關係。只有 trace_id 不夠。每個操作還需要 span_id、父子關係或明確連結、時間、狀態、資源身分與受限屬性。W3C 定義了可互通的 traceparent,以及用於選用廠商狀態的 tracestate;它沒有把不可信請求帶來的 Trace ID 變成授權憑證。好的回答會驗證傳入上下文,在接收層隔離租戶,並定義跨信任邊界的處理方式。
第二個訊號是取樣模型是否誠實。頭端取樣在整條追蹤尚不可見時就作決定,可以降低 SDK、網路 與接收成本,卻不能保證保留稍後才出現的每一條錯誤追蹤。尾端取樣能在看見大部分 Span 後 挑選追蹤,但必須先接收並暫存這些 Span。它降低的是下游儲存成本,不是上游收集成本。如果 2% 的頭端取樣已經丟掉一條追蹤,後續尾端取樣器無法找回其中的錯誤 Span。
第三個訊號是能否真正處理追蹤組裝,而不是畫一條通用事件管線。Span 會重複、延遲、亂序; 非同步扇出和批次處理還可能形成有向無環圖,不一定是一棵整齊的樹。尾端取樣必須把同一條追蹤 的所有 Span 路由到同一個決策者,設定有界的完成判斷、記憶體保護與遲到 Span 規則。這個 有狀態邊界決定平台能提供診斷證據,還是悄悄製造取樣偏差。
最後一個訊號是可維運性。好的回答會量化原始流量與保留流量,限制任意屬性索引,隔離吵鬧 租戶,監測上下文斷裂和 Span 遺失,並避免讓遙測管線成為應用程式相依。只畫到「把 Span 寫入 資料庫」為止,成本、正確性與故障問題都還沒有回答。
回答前需要釐清的問題
- 哪些工作流程必須執行尾端決策? 完整尾端取樣要求中央端先收到所有候選 Span。本方案
對一般流量在來源端做頭端取樣,只讓一組受控的關鍵路由以 100% 進入尾端池。若所有路由都 要求依錯誤保留,中央網路與狀態預算就必須容納完整原始流。
- 「完整追蹤」如何定義? 佇列與脫離請求的工作沒有通用結束標記。同步請求可用根 Span
結束加寬限時間;長時間工作流程需要更長策略或明確完成事件。平台仍要回傳 incomplete 標記,不能把估計當成確定事實。
- 哪些查詢是硬性契約? 範圍內包括依 Trace ID 精確查詢,以及對服務、操作、狀態、耗時
區間與時間的有界篩選。若對每個屬性提供任意全文條件,索引成本和高基數濫用都會放大;未列 入白名單的屬性,只能在取回追蹤後查看。
- 資料保留多久? 假設是七天可索引熱資料,再加 23 天壓縮物件資料。更長的熱資料保留會
改變儲存與索引規模;法規刪除也會決定物件鍵與加密網域是否必須依租戶切分。
- 是否允許跨租戶追蹤? 預設不允許。閘道把驗證憑證綁定到租戶,並覆寫 Span 自帶的租戶
欄位。獲准的跨網域工作流程使用明確 Link 或另行授權的關聯方式,不能信任用戶端指定的租戶 ID。
- 非同步因果關係如何表示? 一個父節點適合一個因果前因。批次消費與匯聚可能依賴多個
生產者,應使用 Span Link。硬選一個父節點會遺失資訊,把同一 Span 複製到多個父節點下會 破壞追蹤計數。
- 過載時應用程式可以損失什麼? 應用程式流量必須繼續。本機佇列耗盡記憶體與磁碟預算後
可以丟遙測,但必須依服務、租戶、原因與取樣類別計數。如果要求遙測零遺失,追蹤就會成為 應用程式路徑相依,可用性契約也必須跟著改變。
30 秒回答架構
「我會拆開上下文傳播、收集、追蹤決策與查詢儲存。埋點函式庫建立 Span 並傳播經驗證的 W3C 上下文,本機 Agent 非同步批次上報並使用有界緩衝,因此中央平台不會阻塞請求路徑。區域閘道 驗證租戶、執行 Schema 與配額檢查,再把 Span 寫入依 Trace ID 分割的持久訊息流。
「一般路由使用一致的頭端取樣降低上游成本;關鍵路由完整進入尾端池。同一條追蹤的所有 Span 會到同一個組裝器,組裝器等待根 Span 加寬限視窗,再依錯誤、耗時與基線預算保留,並記錄追蹤 是否不完整。保留追蹤寫入物件儲存作為標準明細,同時寫入白名單熱索引用於 Trace ID 與有界 條件查詢。我會分別計算取樣前狀態和取樣後儲存,過載時先丟低優先序正常樣本,並用金絲雀追蹤 驗證傳播、遲到 Span、取樣偏差、租戶隔離與可用區復原。」
分步深入解析
步驟一:先確定 Span 契約與查詢 API。
Span 紀錄至少包含:
Span {
tenant_id, trace_id, span_id, parent_span_id?, links[],
service, operation, kind, start_time, end_time, status,
resource_attributes, span_attributes, events[],
observed_at, schema_version, trace_flags, tracestate?
}閘道從驗證資訊導出 tenant_id。它驗證識別碼長度與格式,拒絕過大紀錄,正規化獲准的語意 欄位,並限制屬性數量、值長度、事件數、連結數與總 bytes。平台同時保存事件時間和 observed_at:服務時鐘用來顯示時間軸,收集器時間用來找出延遲與時鐘偏移。SDK 應使用單調 時鐘計算本機 Span 耗時;跨主機排序仍需要牆上時間與因果邊。
主要查詢契約如下:
GetTrace(tenant_id, trace_id)
SearchTraces(tenant_id, start, end, service?, operation?, status?,
min_duration?, max_duration?, cursor?, limit?)GetTrace 回傳 Span、Link、取樣策略與版本、firstobservedat、lastobservedat 和完整性 警告。SearchTraces 必須帶有界時間範圍,並用 cursor 而不是無限 offset 分頁。明細取得與 次要搜尋是兩種不同的存取路徑,不應把一筆寬追蹤紀錄複製到每個次要索引。
步驟二:傳播上下文,同時不把它當成信任。
在 HTTP 中,SDK 擷取並注入 W3C traceparent,其中包含版本、Trace ID、父 Span ID 與旗標; tracestate 攜帶選用的廠商狀態。訊息生產者把相同傳播欄位放進訊息中繼資料。接收端先驗證 格式,再加入追蹤。無效上下文會建立新追蹤並增加傳播錯誤計數,不能污染既有鍵空間。
在網際網路入口或跨租戶邊界,服務可主動建立新的內部追蹤,並對獲准的上游上下文加入 Link。 這樣既保留關聯,也不讓外部呼叫者選擇內部父節點或取樣控制。Baggage 是另一種隨上下文傳播 的鍵值機制。它會扇出到每個下游節點,因此必須有白名單和大小限制,並移除密鑰與個人資料。
優先自動埋點常見 HTTP、RPC、資料庫與佇列函式庫。Dapper 說明了公共函式庫埋點為何能以較低 應用程式負擔提高涵蓋率。自訂 Span 適合應用程式邊界,但平台仍要測量缺少預期伺服器端或 用戶端 Span 的服務與路由。追蹤品質不會超過實際埋點與保留樣本的完整度。
步驟三:讓收集離開應用程式請求路徑。
結束的 Span 先進入程序內有界緩衝區,再以壓縮批次送到節點 Agent 或 Sidecar。Agent 使用 有界磁碟暫存因應短暫收集器故障,以指數退避加抖動重試,並維護不同優先序佇列。應用程式完成 回應前,它不會同步等待中央確認。本機預算耗盡後,先丟低優先序正常遙測,並回報準確的遺失 數量與原因。
區域無狀態閘道驗證 Agent、綁定租戶、執行 bytes 與 Span 配額、驗證 Schema,再把接受的批次 附加到多副本持久訊息流。確認只代表區域訊息流已經持久接收,不代表搜尋已可見。消費者以 (tenantid, traceid, span_id) 加紀錄版本確保冪等;重複投遞可以更新觀測中繼資料,卻不能 產生第二個 Span。
訊息流依 (tenantid, traceid) 的穩定雜湊分割,使一條追蹤只有一個決策者,又不要求全域 Span 順序。尾端取樣擴充時,第一層收集器可依 Trace ID 負載平衡到第二層有狀態取樣器。 OpenTelemetry Collector 也明確要求:同一 Trace 的所有 Span 必須抵達同一個尾端取樣實例。
步驟四:讓每個容量邊界都可重新計算。
取樣前的工作負載為:
500,000 條工作流程/秒 × 15 Span/工作流程 = 7,500,000 Span/秒
7,500,000 Span/秒 × 700 bytes/Span = 5.25 GB/秒
5.25 GB/秒 × 86,400 秒 = 453.6 TB/天原始資料這些是工作負載假設,不是實測壓縮率。若把完整原始流送入中央尾端取樣器,就必須先供應 5.25 GB/秒的接收能力,之後還要考慮副本、協定開銷、重試、傾斜與容錯切換餘裕。
假設一般路由產生 90% Span,採用 2% 一致頭端取樣;關鍵路由產生 10%,以 100% 進入尾端池:
一般流:750 萬 × 90% × 2% = 135,000 Span/秒
尾端池輸入:750 萬 × 10% = 750,000 Span/秒
收集器總輸入:885,000 Span/秒 × 700 bytes = 619.5 MB/秒如果尾端策略平均保留候選 Span 的 10%,熱儲存接收 135,000 + 75,000 = 210,000 Span/秒,也就是 147 MB/秒、12.7008 TB/天,尚未計入壓縮、 副本、索引和物件中繼資料。七天未壓縮等價量為 88.9056 TB。最後以正式環境形態的屬性分布 基準測試決定壓縮與節點數;這組算式只建立下界,並說明各取樣邊界分別省下哪部分成本。
步驟五:把尾端取樣視為成敗關鍵的有狀態瓶頸。
組裝器依租戶與 Trace ID 保存部分狀態:唯一 Span、最早開始、最晚結束、根節點完成狀態、錯誤 狀態、目前耗時、bytes 與最後抵達時間。同步追蹤在根 Span 結束且寬限時間過後可作決定;達到 最大年齡、Span 數或 bytes 時也必須強制決策。長工作流程要有獨立策略,否則一條追蹤就能無限 占用記憶體。
決策順序先替明確關鍵流、錯誤與高延遲追蹤預留容量,再在依服務與租戶切分的預算內,以一致 機率樣本補足基線。「保留全部錯誤」不是有界全域規則,因為事故時錯誤率可能接近 100%。各 類別使用 token bucket 與硬性 bytes 上限控量;容量耗盡時進入可觀測降級策略,不能等到記憶體 不足。
取樣器替遲到 Span 保存決策快取。已保留追蹤的遲到 Span 可以追加並標記追蹤已更新;已丟棄 追蹤的遲到 Span 繼續一致丟棄。快取過期後才抵達的 Span 計為孤兒,不能建立一條誤導性的單 Span 追蹤。每條儲存追蹤攜帶 complete、decision_reason 與遲到 Span 計數。拉長寬限時間 會提高完整度,也會增加記憶體、決策延遲與取樣器故障影響的追蹤數量。
混合管線是最重要的陷阱。尾端邏輯只能從真正抵達的追蹤中挑選。如果上游 2% 頭端取樣在下游 錯誤出現前丟掉某條一般追蹤,尾端階段無法找回它。因此,確實要求依錯誤保留的路由必須不經 丟棄地進入尾端池;或採用獨立觸發機制,並承認它無法重建過去。
步驟六:把標準明細與有界索引分開儲存。
保留 Span 壓實成不可變壓縮物件,依租戶與時間分割,每次追蹤修訂都有 Manifest。Trace ID 目錄把 (tenantid, traceid) 對應到物件位置與最新修訂版,用於精確查詢。近期物件可以 快取,但物件層是衍生索引的重建來源。
熱搜尋索引每條追蹤只保存一列摘要:租戶、Trace ID、根服務與操作、開始時間 bucket、耗時、 狀態、選定服務集合或指紋、取樣原因、完整性與物件指標。只有白名單欄位建立次要索引。任意 使用者 ID、SQL 文字、URL 與 Baggage 值留在受保護明細中或經過遮罩;預設索引這些欄位會造成 無界基數、隱私暴露與寫入放大。
服務相依圖與延遲檢視是保留樣本上的串流彙總,必須標示為取樣估計。機率已知時,取樣權重可 支援部分無偏計數估計;依錯誤與耗時偏置的尾端樣本不會自動代表流量比例。指標系統負責精確 的叢集層級比率,追蹤用來解釋個別因果路徑。
步驟七:隔離租戶並定義故障行為。
閘道限制每個租戶的每秒 bytes、每秒 Span、並行部分追蹤數、查詢並行數與保留 bytes。分割鍵 包含租戶身分,受監管租戶可以使用獨立加密政策,任何索引查詢前都要先授權。某個租戶的巨大 追蹤或高基數屬性,不能排擠其他租戶的取樣狀態。
閘道或可用區故障時,Agent 重試另一個區域端點並使用有界暫存。持久訊息流變慢時,接收控制 降低一般取樣,並在有狀態組裝前拒絕超額 bytes。組裝器故障後,訊息流重播對應分割;checkpoint 加快復原,冪等 Span 鍵吸收重複。索引故障期間,標準物件繼續寫入並累積索引 backlog。精確 查詢近期資料時可回傳「已接收,正在建立索引」,不能把索引尚未更新誤報為不存在。
尾端池過載時,逐步降低機率基線、限制超大追蹤,再對受影響路由降級到確定性頭端策略。控制面 金絲雀追蹤與一部分有界錯誤容量需要保留。平台要依租戶、服務、可用區與策略監測已接收、已 丟棄、重試、過早淘汰、遲到、孤立與完成索引的 Span。
步驟八:驗證結果是否可信,而不只驗證吞吐。
傳播測試涵蓋合法、缺少、格式錯誤與未來版本 Header,跨租戶與網際網路邊界,Baggage 限制, 佇列、重試、扇出、匯聚與批次 Link。組裝測試注入重複、亂序、缺父節點、遲到、過大與永不 結束的追蹤。取樣測試證明整條追蹤決策一致、預算不超限、機率決策可重現、錯誤與耗時策略正確, 以及上游頭端丟棄確實無法復原。
壓力測試應保留正式環境形態的追蹤大小與租戶傾斜,並測量 SDK 成本、Agent 遺失、閘道准入、 訊息流延遲、活躍追蹤記憶體、決策延遲、保留 bytes、索引延遲、Trace ID 查詢與條件搜尋。故障 測試應移除一個可用區、在熱門分割期間重啟組裝器、暫停物件儲存、耗盡租戶配額,並從標準物件 重建熱索引。
持續傳送圖結構已知的合成金絲雀工作流程經過每個區域。預期 Span 遺失、父子關係改變、30 秒 搜尋新鮮度超標或 Trace ID 查詢超過 SLO 時都要告警。收集器程序健康,無法證明追蹤完整或可查。
高品質示範回答
「我會先說明追蹤是 Span 的因果圖,不是一袋日誌。每個 Span 包含租戶綁定的 Trace ID、Span ID、父節點或 Link、時間、狀態、資源、有界屬性、事件與觀測時間。服務透過 HTTP 或訊息中繼 資料傳播經驗證的 W3C 上下文。在不可信邊界,我會新建內部追蹤並加 Link,因為追蹤上下文用於 關聯,不用於授權。
「Span 透過程序內有界緩衝區與本機 Agent 離開請求路徑。Agent 批次處理、壓縮、短暫寫入磁碟, 並在必要時丟棄有計數的低優先序資料,不阻塞應用程式。區域閘道驗證租戶、執行 Schema 與配額, 再寫入多副本訊息流。依租戶和 Trace ID 分割,讓每條追蹤抵達一個組裝器;依 Span ID 冪等處理 重試。
「原始負載是每秒 750 萬個 Span、5.25 GB。我不會在無意間把全部流量送進尾端取樣器。一般 路由使用 2% 一致頭端取樣,受控的 10% 關鍵路由完整進入尾端池,所以收集器輸入是每秒 88.5 萬 個 Span,也就是 619.5 MB。若尾端池保留 10%,儲存每秒接收 21 萬個 Span,原始量約 12.7 TB/天, 還未計算索引與副本。這些數字是基準測試輸入,不是壓縮率承諾。
「組裝器是難點。它保存部分追蹤,等待根節點加寬限視窗,並依年齡、Span 數與 bytes 強制決策。 先替關鍵、錯誤與慢追蹤保留有界容量,再用一致機率基線填滿依服務預算。它快取遲到 Span 的 決策,並標記不完整追蹤。上游 2% 頭端丟棄無法在下游補回,所以真正要求錯誤感知保留的路由, 必須未取樣地進入尾端池。
「保留明細進入壓縮物件儲存,並由 Trace ID 目錄定位。獨立熱索引每條追蹤只存一列摘要,而且 只索引白名單內的服務、操作、狀態、耗時與時間欄位,藉此控制基數並支援重建。故障時 Agent 使用有界暫存,訊息分割可重播,搜尋故障期間物件仍可寫入,過載時先犧牲正常基線。驗收會涵蓋 上下文邊界、重複與遲到 Span、取樣偏差與上限、吵鬧租戶隔離、可用區故障、重建與端對端金絲雀。」
常見錯誤
- 說「先取樣 2%,再由尾端保留所有錯誤」 → 第一階段已經刪除 98% 候選追蹤,其中包含
稍後才出錯的追蹤 → 讓受保護路由不經丟棄進入尾端池,或降低承諾。
- 不依 Trace ID 把 Span 雜湊到不同收集器 → 每個尾端取樣器只看到碎片,決策會偏 →
讓 (tenant, trace_id) 的所有 Span 抵達同一決策者。
- 無限等待完整追蹤 → 非同步工作沒有通用結束標記,狀態會無限成長 → **使用根節點加寬限、
最大年齡與大小、明確工作流程策略和不完整標記。**
- 把
traceparent當成身分或權限 → 外部呼叫者可以自行選擇關聯欄位 → **獨立驗證租戶,
並在信任邊界新建帶 Link 的追蹤。**
- 把 Baggage 或任意屬性加入所有索引 → 基數、寫入放大與敏感資訊暴露都會無界成長 →
只索引白名單欄位,並限制或遮罩傳播值。
- 同步傳送 Span 到中央收集器 → 遙測故障會增加應用程式延遲或降低可用性 → **透過本機
有界佇列非同步批次處理,並準確揭露遺失。**
- 只存每個 Span,不建追蹤摘要 → 服務、耗時與錯誤查詢需要掃描大量明細 → **保留標準明細,
並替每條追蹤建立一列有界且可重建的摘要。**
- 把有偏尾端樣本當成精確流量分布 → 錯誤與耗時規則本來就會過度代表異常追蹤 → **發布
取樣中繼資料,精確彙總比率交由指標系統。**
- 只測試收集器存活 → 上下文斷裂、Span 遺失與索引延遲仍可能不可見 → **執行圖結構已知的
金絲雀,並檢查完整性、取樣、新鮮度與查詢 SLO。**
追問與應對
追問一:產品現在要求保留每一條錯誤追蹤,但中央接收預算不變,怎麼辦?
兩項要求可能衝突。錯誤通常要等下游 Span 執行後才知道,來源端頭端取樣無法保證保留。先量化 最大原始 Span 速率與中央尾端容量。若預算容納不了所有候選路由,就把保證限縮到有界關鍵路由、 擴充容量,或加入應用程式錯誤觸發器,讓之後的取樣率提高,同時承認過去丟棄的 Span 無法重建。 「全部錯誤」也必須有 bytes 上限,因為事故期間幾乎所有流量都可能出錯。
追問二:一則訊息彙整了來自 10 條生產者追蹤的事件,消費者 Span 要選哪個父節點?
沒有一個父節點能代表十個獨立原因。替消費者或批次處理建立對應工作流程 Span,並在 Link 數量 上限內連結十個生產者上下文。如果批次本身有一個傳遞上下文,可把它當父節點,把個別輸入保留 為 Link。查詢與視覺化需要支援有向無環圖,並顯示 Link 遭截斷的資訊;把消費者 Span 複製到 十棵樹會扭曲耗時與儲存。
追問三:尾端取樣器總是在決策視窗前淘汰部分追蹤,如何排查?
比較活躍追蹤數量與 bytes、追蹤大小分布、抵達延遲、熱門分割、決策年齡、過早淘汰計數和租戶 傾斜。直接拉長等待視窗可能讓記憶體壓力更糟。先限制巨大追蹤、隔離吵鬧租戶、在不破壞 Trace 親和性的情況下拆分分割,並降低正常基線;再依遲到 Span 的實際價值擴充容量或縮短視窗。透過 影子策略衡量變更對錯誤、慢追蹤與完整度的影響。
追問四:搜尋故障兩小時,但接收與物件儲存正常,API 應回傳什麼?
繼續寫標準追蹤物件與持久索引變更 backlog。如果 Trace ID 目錄仍可用,GetTrace 可走該 路徑;次要搜尋回傳帶 as_of 的舊資料或明確的暫時無法使用狀態。不能因摘要尚未進入索引, 就聲稱新接收的追蹤不存在。復原後冪等重播並比較數量與延遲;若 backlog 損壞,則從物件重建索引。
追問五:如何證明取樣沒有讓某個低流量服務完全消失?
在共享剩餘全域預算前,先替每個服務或操作保留最低基線預算。每條保留追蹤記錄機率與決策原因, 對「有流量卻沒有保留追蹤」的服務告警。合成金絲雀獨立於機率驗證完整路徑。把追蹤涵蓋情況與 指標請求數比較;某服務有請求卻沒有 Span 時,分別區分缺少埋點、傳播失敗、配額丟棄、頭端 決策、尾端決策與索引遺失。