位元組的分散式鏈路追蹤實踐,教科書式的搭建指南

語言: CN / TW / HK

綜述

位元組跳動在發展過程中,逐漸形成了十分複雜的超大規模微服務體系,對後端整體的可觀測性解決方案提出了極高的要求。為了解決這個問題,基礎架構智慧運維團隊自研鏈路追蹤系統,將海量 Metrics/Trace/Log 資料進行整合與統一,並在此基礎上實現了新一代的一站式全鏈路觀測診斷平臺,幫助業務解決監控排障、鏈路梳理、效能分析等問題。本文將會介紹位元組跳動鏈路追蹤系統的整體功能和技術架構,以及實踐過程中我們的思考與總結。

一、什麼是分散式鏈路追蹤(Trace) ?

1、M T L的關係

可觀測性的三大基礎資料是 Metrics / Log / Trace。說到這三大件,可能大家會想到當需要監控變化趨勢和配置告警時就去用 Metrics;當需要細查問題時去查 log;對於微服務數量較多的系統,還得有 Trace,Trace 也可以看做一種標準結構化的 log,記錄了很多固定欄位,例如上下游呼叫關係和耗時,檢視上下游呼叫關係或者請求耗時在鏈路各節點上的分佈可以檢視 Trace。

但是如果帶著孤立的觀點去用這些資料的話,資料的價值會大大降低。 舉例一個常見的場景,通過 Metrics 得知某服務 SLA 降低,錯誤率上升,怎麼去排查根因呢? 先去找錯誤日誌吧,可是我看到的錯誤日誌是不是真的和這個錯誤率上升有關係呢? 得翻翻程式碼看看這些錯誤日誌都是哪裡打出來的,代表什麼意思。 再去找找有沒有錯誤 Trace? 找出來的 Trace 也不太確定是不是和這個錯誤率上升有關係,還是得看程式碼確認下。 終於通過一行行的程式碼和資料比對,確認到這個錯誤是下一層服務返回給我的,把那個服務的負責人拉進來一起排查吧,然後這個群越拉越大,更多的人被拖進來一層一層地查下去,最終定位到是某個底層服務上線了一個變更導致 Panic,錯誤層層向上傳播導致服務 SLA 降低。

這個過程很不美好,需要工程師理解每一項資料的底層邏輯,才能充分利用它們去解決具體問題。而在複雜的大規模微服務系統中,沒有單個工程師能夠做到熟悉每一個微服務的底層邏輯,因此複雜微服務系統的排障和觀測往往是一項有挑戰的困難工作。

2、Trace 是資料的連結紐帶

如果所有微服務的監控資料都是遵循統一模型和語義規範並且天生高度關聯的呢?

在軟體系統中,每秒鐘有無數的 Context 在流動。這些 Context 可能是一個實時線上請求,也可能是一個非同步處理任務。每個 Context 都會在多個微服務節點中持續傳播才能最終完成。所有的監控資料(包括 Metric, Log 等)都源自於某一個 Context。Trace 就是這個 Context 的資料載體,通過標準化的資料模型,記錄 Context 在多個微服務中的全部執行過程,並沿途關聯上此 Context 上發生的所有事件(包括 Metric, Log 等)。

再回到剛才那個 Case,當我們對某個 Metric 波動發生興趣時,可以直接將造成此波動的 Trace 關聯檢索出來,然後檢視這些 Trace 在各個微服務中的所有執行細節,發現是底層某個微服務在執行請求過程中發生了 Panic,這個錯誤不斷向上傳播導致了服務對外 SLA 下降。如果可觀測平臺做得更完善一些,將微服務的變更事件資料也呈現出來,那麼一個工程師就可以快速完成整個排障和根因定位的過程,甚至不需要人,通過機器就可以自動完成整個排障和根因定位過程。

Trace 不僅僅是用來檢視耗時分佈甘特圖的工具, 也是海量監控資料的 Context 連結紐帶。 基於可靠關聯的 Metric / Trace / Log 資料,也構建出強大的可觀測效能力,回答監控排障、SLO 調優、架構梳理、流量估算、智慧化故障歸因等眾多複雜問題。

Trace 的採集以及跨服務程序的 Context 傳遞一般是由微服務框架等基礎設施自動完成的,但是要實現最佳效果也需要所有研發工程師的理解和配合。研發工程師在編碼的過程中應當有意識地在所有程式碼執行過程中持續傳遞 Context。比如在 Golang 中,context.Context 需要在所有函式呼叫中作為引數持續傳遞;在 Java 中,一般預設用 Threadlocal 作為 Context 的儲存載體,但是如果有多執行緒或者非同步的場景,則需要開發者自行對 Context 進行顯式的傳遞,否則上下文就斷了,難以實現有效的追蹤和監控。

二、位元組鏈路追蹤系統的挑戰與機遇

位元組跳動在發展過程中,逐漸形成了十分複雜的超大規模微服務體系,對後端整體的可觀測性解決方案提出了極高的要求。

我們面臨的挑戰包括:

  • 線上流量巨大

  • 微服務數量巨大,呼叫關係複雜,迭代變化快

  • 研發團隊龐大,分工複雜

目前位元組跳動有巨大的流量,眾多的活躍微服務、容器例項數,以及龐大的研發團隊。一個複雜業務鏈路動輒涉及數百個微服務,有一線業務,有中臺,也有基礎設施,不同微服務由不同的研發團隊開發,同時還有各類橫向團隊負責整體架構的質量、穩定性、安全和效率等相關工作。不同團隊對鏈路追蹤系統都會有不一樣的訴求。

同時我們也有著難得的機遇:

  • 微服務框架高度統一

  • 微服務高度容器化,環境統一

  • 儲存/中介軟體等基礎設施高度統一

得益於長期的統一基建工作,位元組全公司範圍內的所有微服務使用的底層技術方案統一度較高。絕大部分微服務都部署在公司統一的容器平臺上,採用統一的公司微服務框架和網格方案,使用公司統一提供的儲存元件及相應 SDK。高度的一致性對於基礎架構團隊建設公司級別的統一鏈路追蹤系統提供了有利的基礎。

三、位元組鏈路追蹤系統的目標

面對這樣的現狀,位元組鏈路追蹤系統圍繞著一些目標展開建設。我們的功能性目標主要包括這幾個方面:

  • 統一資料模型與語義: 統一資料模型和語義規範,對所有主流框架/元件進行預設埋點中介軟體的替換升級,建立 Metrics / Trace / Log 可靠關聯關係。

  • 開放自定 義: 統一模型的基礎上,充分開放自定義能力,滿足不同業務場景的監控追蹤需求。

  • 中心化配置管控: 中心化動態管理取樣、染色、熔斷限流、索引、脫敏保密等各類策略。

  • 一站式觀測平臺: 提供從 SDK 到採集、計算、儲存、查詢和產品化互動的完整解決方案,基於高質量基礎資料,構建一站式觀測平臺,提升監控排障、SLO 調優、架構梳理、容量管理等場景的能效。

在功能性目標的背後,我們追求的技術目標主要圍繞這幾個方面:

  • 業務整合開銷最小 化: 整合開銷包括業務接入的改造成本和接入後帶來的 Overhead 開銷。大範圍的鏈路追蹤能夠成功覆蓋推廣,必須保證將整合開銷降到最低。

  • 平衡儲存效率與檢索需求: 需要以有限的機器預算完成較大資料量的處理和儲存,保證資料從產生到可被檢索的延遲在分鐘級以內,檢索響應速度在秒級以內。

  • 多機房容災完備性: 需要優先考慮當發生斷網或擁塞、機房宕機等災難場景,業務急需觀測線上狀況時,保持可用。

  • 最小化架構與依賴複雜度: 位元組在海內外有眾多機房,需儘可能最小化整體架構的複雜度和第三方依賴的複雜度,否則多機房的部署運維包括容災完備性保障會非常困難。

四、位元組鏈路追蹤系統的實現

1、資料採集

1)資料模型

統一的資料模型是 Trace 的基礎,位元組鏈路追蹤系統的資料模型設計借鑑了 opentracing 和 CAT 等優秀的開源解決方案,結合位元組內部實際生態和使用習慣,使用如下資料模型:

  • Span: 一個有時間跨度的事件,例如一次 RPC 呼叫,一個函式執行。

  • Event: 一個沒有時間跨度的事件,例如一條 log,一次 panic。

  • Metric: 一個帶多維 tag 的數值,例如一個訊息體的大小,一個訂單的價格。

  • Trace: 一個請求上下文在多個分散式微服務節點的完整執行鏈路。

  • Transaction: 一條 Trace 在單個服務節點上的所有 Span / Event / Metric 物件構成的樹形結構訊息體。Transaction 是 Trace 資料的處理和儲存的最小單位,緊湊的資料結構有利於節約成本和提高檢索效能。

下圖展示了使用位元組鏈路追蹤系統 SDK 埋 Trace 的程式碼示例。 注意其中 Context 貫穿整個請求生命週期,在程序內和跨程序間持續傳遞,將資料串聯起來。

繼續這個示例,我們結合下圖闡述一下如何基於這套模型將 Metric / Trace / Log 進行可靠關聯的。

①Metric 關聯 Trace:

  • 每個 Span 會有內建的頻次/耗時/失敗率 Metric 統計時序,Rpc/Mq 場景的 Span 還會有 SendSize/RecvSize/MqLag 等內建統計時序。Span Tag 和 Metric Tag 一一對應,以此為依據可以將 Span 時序指標與 Trace 中的 Span 可靠關聯。

  • 每個 Event 不僅會掛載在 Span 上,也會有內建的頻次 Metric 統計時序。Event Tag 與 Metric Tag 一一對應,以此為依據可以將 Event 時序指標與 Trace 中的 Event 可靠關聯。

  • 每個 Metric 不僅會掛載在 Span 上,也會按 Metric 型別輸出 rate/timer/store 等各類統計時序,兩邊 Tag 一一對應,以此為依據可以將 Metric 時序指標與 Trace 中的 Metric 物件可靠關聯。

②Trace 關聯 Log:

  • Log SDK 會將 Context 中的 TraceID 和 SpanID 寫入日誌頭中,通過 TraceID 和 SpanID 與 Trace 建立可靠關聯。

2)語義規範

僅有統一的抽象資料模型還不夠。如果每個服務都五花八門的隨意打 tag 沒有統一標準,那麼即使有統一抽象模型也很難建設高質量的觀測平臺。必須對 HTTP Server, RPC Server, RPC Client, MySQL Client, Redis Client, MQ Consumer, MQ Producer 等各類主流場景都進行統一的語義規範,確保不同語言不同框架在相同場景下上報的資料遵循統一語義規範,才能夠真正獲取高質量的可觀測性資料。

語義規範沒有唯一標準,下面給出位元組內部目前使用的部分語義規範作為參考示例。

①通用基礎欄位

②場景化語義規範示例:RPC Client 場景

3)取樣策略

由於位元組整體線上流量非常大,微服務數目眾多,不同微服務的效能敏感度、成本敏感度和資料需求各有不同,例如有些服務涉及敏感資料,必須有非常完整的追蹤資料;有些服務效能高度敏感,需要優先控制取樣數最小化 Overhead;測試泳道、小流量灰度或者線上問題追查等場景會需要不同的取樣策略;常規流量和發生異常的流量也需要不同的取樣策略。因此靈活的取樣策略以及調控手段非常必要。位元組鏈路追蹤系統主要提供瞭如下幾種取樣模式:

  • 固定概率取樣+低流量介面兜底取樣: 預設以 Logid 作為取樣種子,按固定概率進行取樣。對於流量較低的介面,按固定概率取樣難以命中的,SDK 會自動按一定的時間間隔進行兜底取樣,確保低流量介面也有一定數目的請求被採集。

  • 自適應概率取樣: 按單位時間對每個介面採集一定數目的 Transaction 為目標,例如 100 條/min,SDK 自動根據當前 QPS 動態計算取樣率進行取樣。流量較低或不穩定的服務建議採取這種模式。

  • 對特定的請求新增染色標記,SDK 檢測到染色標對該請求進行強制取樣。

我們結合一個示例來更好的理解什麼是 PostTrace。左圖是一個請求,按照阿拉伯數字標識的順序在微服務間發生了呼叫,本來這條 trace 沒有采樣,但是在階段 5 時發生了異常,觸發了 posttrace,這個 posttrace 資訊可以從 5 回傳到 4,並傳播給後續發生的 6 和 7,最後再回傳到 1,最終可以採集到 1,4,5,6,7 這幾個環節的資料,但是之前已經結束了的 2、3 環節則採集不到。右圖是我們線上的一個實際的 posttrace 展示效果,錯誤層層向上傳播最終採集到的鏈路的樣子。PostTrace 對於錯誤鏈傳播分析、強弱依賴分析等場景有很好的應用。

這些取樣策略可以同時組合使用。 需注意,取樣不影響 Metrics 和 Log。 Metrics 是全量資料的聚合計算結果,不受取樣影響。 業務日誌也是全量採集,不受取樣影響。

4)中心化配置管控

為了提高效率,方便不同團隊高效工作,位元組鏈路追蹤系統提供了豐富的中心化配置管控能力,核心能力包括以下幾個方面: