可觀測性與追蹤

分散式追蹤(distributed tracing)

在一個小程式裡,你讀一份堆疊追蹤就能跟著一個請求走。但現代系統處理一次使用者點擊,是把它傳遞給十幾個各自獨立的服務——一個網頁前端呼叫一個認證服務,後者呼叫一個資料庫,再呼叫一個快取,全在不同機器上。當那次點擊很慢時,沒有任何單一機器的日誌能告訴你時間花在哪裡。分散式追蹤解決這點的方法,是跟著「一個」請求從頭到尾、隨它在各服務間跳躍,把所有跳躍縫成一條連貫的時間線。

用淺白步驟說明這個機制。工作的單位是 span:一個具名操作,帶有開始時間、持續時間、以及一些屬性(例如花了 790ms 的 span「charge-card」)。一個 trace 是一個請求所有 span 構成的整棵樹,全共用一個共同的 trace id,而每個 span 都記錄它父 span 的 id——於是你能重建誰呼叫了誰、每一步花了多久。關鍵訣竅是追蹤上下文傳播(trace context propagation):當服務 A 呼叫服務 B 時,它把 trace id 與當前 span id 一起傳過去,通常放在一個請求標頭裡(W3C 的 traceparent 標頭是標準)。服務 B 讀那個標頭,把自己的工作做成 A 那個 span 的子 span,並把上下文繼續往它呼叫的對象傳。一致地做下去,每個服務都在同一個 trace id 底下貢獻它的 span,而一個追蹤後端把它們組裝成一張瀑布圖,確切顯示哪個服務、哪一次呼叫主宰了這個請求的延遲。

它之所以重要,是因為在一個多服務系統裡,分散式追蹤往往是回答「為什麼這個特定請求很慢?」的「唯一」方法——它把延遲定位到確切的那一跳,並浮現出任何單服務視角看不到的問題,比如一個慢的下游相依把一切都拖住。誠實的提醒:追蹤只在「每個」服務都傳播上下文時才有效——一個丟掉標頭的服務就斷了鏈,你會得到一條破碎的 trace;在規模下替每個請求都捕捉 span 很貴,所以系統會對 traces 取樣(保留一部分),這代表你想要的那條特定 trace 可能沒被保留,除非取樣偏向錯誤與慢請求;而一條 trace 告訴你時間跨服務「花在哪裡」,但你往往仍需要對應的日誌或一份剖析,才能知道在那個慢服務「裡面為什麼」。

trace id a1b2(一次使用者點擊): [frontend 820ms ] [auth 12ms] [checkout 760ms ] [charge-card 740ms ] <- 那一跳很慢 [write-receipt 18ms] # A->B 傳遞的標頭:traceparent: 00-a1b2...-<span-id>-01

共用一個 trace id 的各 span 組成瀑布圖;跨服務傳播 traceparent 把那 740ms 的成本定位到 charge-card 那一跳。

分散式追蹤的完整度只取決於它最弱的一環:一個沒能傳播追蹤上下文的服務就斷了鏈,而在規模下 traces 通常被取樣,所以「那個請求沒有 trace」往往代表它沒被保留,而不是什麼都沒發生。

又稱
the spantrace context propagationrequest tracing跨服務追蹤span 與追蹤上下文