有贊全鏈路追蹤實踐

一、簡介

在企業級業務系統日趨複雜的背景下,微服務架構逐漸成為了許多中大型企業的標配,它將龐大的單體應用拆分成多個子系統和公共的元件單元。這一理念帶來了許多好處:複雜系統的拆分簡化與隔離、公共模組的重用性提升與更合理的資源分配、大大提升了系統變更迭代的速度、更靈活的可擴充套件性以及在雲計算中的適用性,等等。

但是微服務架構也帶來了新的問題:拆分後每個使用者請求可能需要數十個子系統的相互呼叫才能最終返回結果,如果某個請求出錯可能需要逐個子系統排查定位問題;或者某個請求耗時比較高,但是很難知道時間耗在了哪個子系統中。全鏈路追蹤系統就是為了解決微服務場景下的這些問題而誕生的。一般地,該系統由幾大部分組成:

客戶端埋點 SDK

:整合在各業務應用系統中,完成鏈路追蹤、資料採集、資料上報等功能;

實時資料處理系統

:對客戶端採集上來的資料進行實時計算和相關處理,建立必要索引和儲存等;

使用者互動系統

:提供使用者互動介面,供開發、測試、運維等使用者最終使用鏈路追蹤系統提供的各項功能;

離線分析系統

:對鏈路追蹤資料進行離線分析,提供諸多強大的鏈路統計分析和問題發現功能;

二、多語言

有贊目前的應用型別有很多種,已經支援追蹤的語言有 Java、Node。js、PHP。那麼,如何讓跨不同語言的呼叫鏈路串到一起呢?有讚的鏈路追蹤目前在使用的是 Cat 協議,業界也已經有比較成熟的開源協議:OpenTracing,OpenTracing 是一個“供應商中立”的開源協議,使用它提供的各語言的 API 和標準資料模型,開發人員可以方便的進行鏈路追蹤,並能輕鬆打通不同語言的鏈路。

Cat 協議與 OpenTracing 協議在追蹤思路和 API 上是大同小異的。基本思路是 Trace 和 Span:Trace 標識鏈路資訊、Span 標識鏈路中具體節點資訊。一般在鏈路的入口應用中生成 traceId 和 spanId,在後續的各節點呼叫中,traceId 保持不變並全鏈路透傳,各節點只產生自己的新的 spanId。這樣,透過 traceId 唯一標識一條鏈路,spanId 標識鏈路中的具體節點的方式串起整個鏈路。一個簡單的示意圖如下:

有贊全鏈路追蹤實踐

實際每個節點上報的資料中還包含一些其他資訊,比如:時間戳、服務標識、父子節點的 id 等等。

三、客戶端埋點 SDK

3。1 Java Agent 與 Attach API

Java Agent 一般透過在應用啟動引數中新增 -javaagent 引數新增 ClassFileTransformer 位元組碼轉換器。JVM 在類載入時觸發 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件呼叫新增的位元組碼轉換器完成位元組碼轉換,該過程時序如下:

有贊全鏈路追蹤實踐

Java Agent 所使用的 Instrumentation 依賴 JVMTI 實現,當然也可以繞過 Instrumentation 直接使用 JVMTI 實現 Agent。JVMTI 與 JDI 組成了 Java 平臺除錯體系(JPDA)的主要能力。

有讚的應用啟動指令碼都是由業務方各自維護,因此要在成百上千的應用啟動指令碼中新增 -javaagent 引數是個不小的工作量。Java 從 Java 6 開始提供了 JVMAttachAPI ,可以在執行時動態 attach 到某個 JVM 程序上。 AttacheAPI 需要的程序 pid 引數,如果獲取當前程序的 pid 是一個難點。不同的 JVM 版本有不同的方式,比較方便的是,位元組碼增強框架 Byte-Buddy 已經封裝好了上述過程: JMX 或 java。lang。ProcessHandle 介面獲取當前程序的 pid,只需要使用 ByteBuddyAgent。install() 就能 attach 到當前程序。

Byte-Buddy 是一個優秀的位元組碼增強框架,基於 ASM 實現,提供了比較高階的 subClass()、redefine()、rebase() 介面,並抽象了強大豐富的 Class 和 Method 匹配 API。

3.2 透明升級

有贊內部的框架和中介軟體元件已經進行統一託管,由專門的 Jar 包容器負責託管載入工作,幾乎所有 Java 應用都接入了該 Jar 包容器,Jar 包容器在應用的類載入之前啟動。藉助 Jar 包容器提供的入口,鏈路追蹤的 SDK 在應用啟動之前完成位元組碼轉換器的裝載工作,同時 SDK 也託管在該 Jar 包容器中,進而在實現應用無感知的追蹤同時,又實現了全鏈路追蹤 SDK 的透明升級。整個帶起過程如下圖所示:

有贊全鏈路追蹤實踐

應用無感知的自動化追蹤與透明升級方式,大大提升了後續迭代的速度,應用接入成本降到 0、追蹤應用的比例接近 100%,為後續發展帶來了更多可能。與非透明方式的追蹤對比如下:

有贊全鏈路追蹤實踐

3.3 非同步呼叫追蹤

請求在一個程序內部可能會有多個子呼叫,Trace 資訊在程序內部共享一般是透過 ThreadLocal 實現的,但是當有非同步呼叫情形時,這部分呼叫可能就會在鏈路中丟失。我們需要做的是跨執行緒透傳 Trace 資訊,雖然 JDK 內建的 InheritableThreadLocal 支援父子執行緒傳遞,但是當面對執行緒池中執行緒複用的場景時還是不能滿足需求。

比較常見的解決方案是 Capture/Replay 模型:在建立非同步執行緒時將當前執行緒上下文進行 Capture 快照,並傳遞到子執行緒中,在子執行緒執行時先透過 Replay 回放設定傳遞過來快照資訊到當前上下文。具體流程如圖:

有贊全鏈路追蹤實踐

因為內部有通用的執行緒工具類 FrameworkRunnable 和 FrameworkCallable ,因此只需要對這兩個工具類進行統一增強就可以滿足大部分非同步呼叫的追蹤場景。另外提供了非同步處理工具 AsyncUtil ,在使用了自定義執行緒時,使用 AsyncUtil。wrap() 對自定義執行緒進行包裝即可實現 Capture/Replay 的過程。

3.4 遇到的問題

包衝突問題

:鏈路追蹤 SDK 依賴的一些包可能會與業務系統的依賴發生版本衝突,比如 SDK 依賴的 Byte-Buddy 框架在很多應用中也有間接依賴,SDK 在使用過程中載入的 Jar 可能會與應用依賴的版本衝突。使用 maven 的 shade 外掛可以有效的解決這個問題,shade 外掛基於 ASM 實現,透過掃描 SDK Class 中的 import 引用進行包名替換,可以有效避免這個問題,詳細的使用用法請參照 maven-shade-plugin 文件;

API 耦合問題

:除了 Java Agent 透明追蹤外,某些場景需要提供 API 給業務系統顯式呼叫,而 API 要完成相關功能就必須與 SDK 的邏輯有互動,這樣業務系統就必須透過 API 間接依賴 SDK。這時可以提供空的 API 實現,然後用 SDK 增強 API 實現原來的邏輯,將原來要依賴 SDK 才能實現的邏輯透過執行時位元組碼增強注入到 API 實現中。方便的解耦 API 與 SDK 依賴耦合問題;

Child-first 類載入可能死鎖問題

:如果 Jar 包容器沒有遵循雙親委派模型,而鏈路追蹤的 SDK 又是由 Jar 包容器託管載入的,則可能因為位元組碼增強本身需要載入類並且類載入過程中的鎖機制導致執行緒死鎖,這時需要在 ClassFileTransformer 中進行 Classloader 過濾;

四、系統間整合

4.1 與統一接入系統整合

統一接入系統是有贊外部流量的接入代理系統。

因為鏈路有采樣率設定,有時在測試或排查線上問題時不方便。為了支援鏈路 100% 取樣,我們支援在前端頁面請求的 HTTP Header 中設定類似 -XXDebug 引數,統一接入系統判斷在 HTTP 請求頭中包含特定的 -XXDebug 引數時,生成符合鏈路取樣特徵規則的 traceId 從而實現測試請求 100% 取樣。

4.2 與日誌系統整合

為了使業務系統在上報日誌給天網日誌系統時自動記錄 traceId,鏈路追蹤 SDK 在開啟追蹤後會將 traceId put 到 MDC 中,天網日誌 SDK 在記錄日誌時會獲取 MDC 中的 traceId,並作為日誌的描述資料一起上報,並進行索引。在日誌查詢時,支援按 traceId 查詢。

同時天網日誌系統對每次查詢的結果資料頁的日誌 traceId 進行批次計算,判斷哪些日誌記錄對應的請求在鏈路追蹤系統中被取樣,對取樣過的日誌記錄的 traceId 替換成超連結,支援一鍵跳轉到對應的鏈路詳情頁。

五、資料處理架構

鏈路資料處理使用 Spark Streaming 任務準實時計算,處理過的資料進行 ES 索引,並存儲在 Hbase 中。資料上報過程使用常見的本地資料上報 agent + remote collector 的方式,然後匯到 kafka 佇列供實時任務處理,架構檢視如下所示:

有贊全鏈路追蹤實踐

本地 agent

之所以採用本地 agent 與遠端 collector 方式,一方面考慮到大流量資料上報時的網路擁塞,使用本地 agent,SDK 可以將資料傳送給 agent 由 agent 非同步傳送出去,減輕了 SDK 中資料上報佇列溢位的風險;另一方面,如果資料直接從應用端連線到 kafka 佇列,整體架構雖然變簡單了,但是 kafka 所能承受的連線數比較有限,隨著應用數的增長,整體架構會變得無法擴容。

鏈路最佳化

資料上報的鏈路經歷了多次迭代最佳化,因為不同語言客戶端上報資料的格式問題,舊版本的鏈路很長,多了幾步資料格式轉換與轉發的過程。隨著鏈路的最佳化迭代,每減少一個轉發與轉換的環節對於有讚的資料量都能節省許多資源。

資料處理最佳化

資料處理部分舊版本使用的是 Java 任務,改成 Spark Streaming 處理資料後整體計算資源的消耗也節省了原來的一半,包括 CPU 核數與記憶體總量。

六、總結與展望

全鏈路追蹤系統包含幾大部分:鏈路採集 SDK、資料處理服務、使用者產品。SDK 部分比較偏技術。資料處理更考驗資料處理的吞吐能力和儲存的容量,採用更高階的鏈路取樣解決方案可以有效降低這部分的成本。使用者產品主要考驗的是設計者對使用者需求的把握,全鏈路追蹤可以做很多事情,產品上可以堆疊出很多功能,怎樣能讓使用者快速上手,簡潔而又易用是鏈路追蹤產品設計的一大挑戰。未來一段時間,有贊全鏈路追蹤會圍繞以下幾個方面繼續演進:

賦能有贊雲

:給有贊雲開發者使用者提供有容器應用的鏈路追蹤能力;

開源協議支援

:資料模型與鏈路追蹤 API 遷移到 OpenTracing 協議上,支援更多新語言的快速追蹤;

使用者產品迭代

:持續提升產品體驗,提供更切合使用者使用場景的產品;

支援更多元件

:支援更多元件和中介軟體的追蹤能力;

離線分析能力

:基於鏈路追蹤的資料,挖掘離線分析能力,提供更豐富和強大的功能;

原文連結:https://www。tuicool。com/articles/Y3y6VnN

有贊全鏈路追蹤實踐

每天都會有更新看過的朋友可以關注一波,Java學習路線和優質資源評論或後臺回覆“java”獲取