Categories
程式開發

有贊全鏈路追踪實踐


一、簡介

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

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

  • 客戶端埋點SDK:集成在各業務應用系統中,完成鏈路追踪、數據採集、數據上報等功能;
  • 實時數據處理系統:對客戶端採集上來的數據進行實時計算和相關處理,建立必要索引和存儲等;
  • 用戶交互系統:提供用戶交互界面,供開發、測試、運維等用戶最終使用鏈路追踪系統提供的各項功能;
  • 離線分析系統:對鏈路追踪數據進行離線分析,提供諸多強大的鏈路統計分析和問題發現功能;

二、多語言

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

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

有贊全鏈路追踪實踐 1

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

三、客戶端埋點SDK

3.1 Java Agent與Attach API

Java Agent一般通過在應用啟動參數中添加 -javaagent參數添加 ClassFileTransformer字節碼轉換器。 JVM在類加載時觸發 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件調用添加的字節碼轉換器完成字節碼轉換,該過程時序如下:

有贊全鏈路追踪實踐 2

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

有讚的應用啟動腳本都是由業務方各自維護,因此要在成百上千的應用啟動腳本中添加 -javaagent 參數是個不小的工作量。 Java 從 Java 6 開始提供了 JVMAttachAPI,可以在運行時動態 attach 到某個JVM進程上。AttacheAPI需要的進程pid參數,如果獲取當前進程的pid是一個難點。不同的JVM版本有不同的方式,比較方便的是,字節碼增強框架Byte-Buddy已經封裝好了上述過程:JMXjava.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的透明昇級。整個帶起過程如下圖所示:

有贊全鏈路追踪實踐 3

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

優缺點 非透明方式 Java Agent方式
埋點方式 dubbo Filter + spring 攔截器 + AOP + Javassist…… 統一的接口和增強模型
接入成本 強依賴(Maven、Gradle)+ 手動配置 透明接入
升級方式 逐個應用升級 透明昇級

3.3 異步調用追踪

請求在一個進程內部可能會有多個子調用,Trace信息在進程內部共享一般是通過 ThreadLocal實現的,但是當有異步調用情形時,這部分調用可能就會在鏈路中丟失。我們需要做的是跨線程透傳Trace信息,雖然 JDK 內置的 InheritableThreadLocal支持父子線程傳遞,但是當面對線程池中線程復用的場景時還是不能滿足需求。

比較常見的解決方案是 Capture/Replay模型:在創建異步線程時將當前線程上下文進行 Capture快照,並傳遞到子線程中,在子線程運行時先通過 Replay回放設置傳遞過來快照信息到當前上下文。具體流程如圖:

有贊全鏈路追踪實踐 4

因為內部有通用的線程工具類 FrameworkRunnableFrameworkCallable,因此只需要對這兩個工具類進行統一增強就可以滿足大部分異步調用的追踪場景。另外提供了異步處理工具 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 putMDC中,天網日誌SDK在記錄日誌時會獲取MDC中的traceId,並作為日誌的描述數據一起上報,並進行索引。在日誌查詢時,支持按traceId查詢。

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

五、數據處理架構

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

有贊全鏈路追踪實踐 5

  • 本地agent 之所以採用本地agent與遠程collector方式,一方面考慮到大流量數據上報時的網絡擁塞,使用本地agent,SDK可以將數據發送給agent由agent異步發送出去,減輕了SDK中數據上報隊列溢出的風險;另一方面,如果數據直接從應用端連接到kafka隊列,整體架構雖然變簡單了,但是kafka所能承受的連接數比較有限,隨著應用數的增長,整體架構會變得無法擴容。
  • 鏈路優化 數據上報的鏈路經歷了多次迭代優化,因為不同語言客戶端上報數據的格式問題,舊版本的鏈路很長,多了幾步數據格式轉換與轉發的過程。隨著鏈路的優化迭代,每減少一個轉發與轉換的環節對於有讚的數據量都能節省許多資源。
  • 數據處理優化 數據處理部分舊版本使用的是Java任務,改成Spark Streaming處理數據後整體計算資源的消耗也節省了原來的一半,包括CPU核數與內存總量。

六、總結與展望

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

  • 賦能有贊雲:給有贊雲開發者用戶提供有容器應用的鏈路追踪能力;
  • 開源協議支持:數據模型與鏈路追踪API遷移到OpenTracing協議上,支持更多新語言的快速追踪;
  • 用戶產品迭代:持續提升產品體驗,提供更切合用戶使用場景的產品;
  • 支持更多組件:支持更多組件和中間件的追踪能力;
  • 離線分析能力:基於鏈路追踪的數據,挖掘離線分析能力,提供更豐富和強大的功能;

本文轉載自公眾號有贊coder(ID:youzan_coder)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455760578&idx=1&sn=41260bbedd7763bdba8c48eed39ee857&chksm=8c6868e7bb1fe1f141659dc043b1f8046d03c4ed921d41b244bbd4b94e37e49108e169ae0f1f&scene=27#wechat_redirect