分布式链路追踪框架的基本实现原理
目錄
分布式追蹤
分布式系統(tǒng)
分布式追蹤
分布式追蹤有什么用呢
什么是分布式追蹤
Dapper
分布式追蹤系統(tǒng)的實(shí)現(xiàn)
跟蹤樹和 span
Jaeger 和 OpenTracing
OpenTracing 數(shù)據(jù)模型
Span 格式
Trace
OpenTracing
Jaeger 結(jié)構(gòu)
Span
OpenTracing API
分布式追蹤
什么是分布式追蹤
分布式系統(tǒng)
當(dāng)我們使用 Google 或者 百度搜索時(shí),查詢服務(wù)會(huì)將關(guān)鍵字分發(fā)到多臺(tái)查詢服務(wù)器,每臺(tái)服務(wù)器在自己的索引范圍內(nèi)進(jìn)行搜索,搜索引擎可以在短時(shí)間內(nèi)獲得大量準(zhǔn)確的搜索結(jié)果;同時(shí),根據(jù)關(guān)鍵字,廣告子系統(tǒng)會(huì)推送合適的相關(guān)廣告,還會(huì)從競(jìng)價(jià)排名子系統(tǒng)獲得網(wǎng)站權(quán)重。通常一個(gè)搜索可能需要成千上萬(wàn)臺(tái)服務(wù)器參與,需要經(jīng)過(guò)許多不同的系統(tǒng)提供服務(wù)。
多臺(tái)計(jì)算機(jī)通過(guò)網(wǎng)絡(luò)組成了一個(gè)龐大的系統(tǒng),這個(gè)系統(tǒng)即是分布式系統(tǒng)。
在微服務(wù)或者云原生開發(fā)中,一般認(rèn)為分布式系統(tǒng)是通過(guò)各種中間件/服務(wù)網(wǎng)格連接的,這些中間件提供了共享資源、功能(API等)、文件等,使得整個(gè)網(wǎng)絡(luò)可以當(dāng)作一臺(tái)計(jì)算機(jī)進(jìn)行工作。
分布式追蹤
在分布式系統(tǒng)中,用戶的一個(gè)請(qǐng)求會(huì)被分發(fā)到多個(gè)子系統(tǒng)中,被不同的服務(wù)處理,最后將結(jié)果返回給用戶。用戶發(fā)出請(qǐng)求和獲得結(jié)果這段時(shí)間是一個(gè)請(qǐng)求周期。
當(dāng)我們購(gòu)物時(shí),只需要一個(gè)很簡(jiǎn)單的過(guò)程:
獲取優(yōu)惠劵 -> 下單 -> 付款 -> 等待收貨然而在后臺(tái)系統(tǒng)中,每一個(gè)環(huán)節(jié)都需要經(jīng)過(guò)多個(gè)子系統(tǒng)進(jìn)行協(xié)作,并且有嚴(yán)格的流程。例如在下單時(shí),需要檢查是否有優(yōu)惠卷、優(yōu)惠劵能不能用于當(dāng)前商品、當(dāng)前訂單是否符合使用優(yōu)惠劵條件等。
下圖是一個(gè)用戶請(qǐng)求后,系統(tǒng)處理請(qǐng)求的流程。
【圖片來(lái)源:鷹眼下的淘寶分布式調(diào)用跟蹤系統(tǒng)介紹】
圖中出現(xiàn)了很多箭頭,這些箭頭指向了下一步要流經(jīng)的服務(wù)/子系統(tǒng),這些箭頭組成了鏈路網(wǎng)絡(luò)。
在一個(gè)復(fù)雜的分布式系統(tǒng)中,任何子系統(tǒng)出現(xiàn)性能不佳的情況,都會(huì)影響整個(gè)請(qǐng)求周期。根據(jù)上圖,我們?cè)O(shè)想:
1.系統(tǒng)中有可能每天都在增加新服務(wù)或刪除舊服務(wù),也可能進(jìn)行升級(jí),當(dāng)系統(tǒng)出現(xiàn)錯(cuò)誤,我們?nèi)绾味ㄎ粏?wèn)題?
2.當(dāng)用戶請(qǐng)求時(shí),響應(yīng)緩慢,怎么定位問(wèn)題?
3.服務(wù)可能由不同的編程語(yǔ)言開發(fā),1、2 定位問(wèn)題的方式,是否適合所有編程語(yǔ)言?
分布式追蹤有什么用呢
隨著微服務(wù)和云原生開發(fā)的興起,越來(lái)越多應(yīng)用基于分布式進(jìn)行開發(fā),但是大型應(yīng)用拆分為微服務(wù)后,服務(wù)之間的依賴和調(diào)用變得越來(lái)越復(fù)雜,這些服務(wù)是不同團(tuán)隊(duì)、使用不同語(yǔ)言開發(fā)的,部署在不同機(jī)器上,他們之間提供的接口可能不同(gRPC、Restful api等)。
為了維護(hù)這些服務(wù),軟件領(lǐng)域出現(xiàn)了?Observability?思想,在這個(gè)思想中,對(duì)微服務(wù)的維護(hù)分為三個(gè)部分:
度量(Metrics):用于監(jiān)控和報(bào)警;
分布式追蹤(Tracing):用于記錄系統(tǒng)中所有的跟蹤信息;
日志(Logging):記錄每個(gè)服務(wù)只能中離散的信息;
這三部分并不是獨(dú)立開來(lái)的,例如 Metrics 可以監(jiān)控 Tracing 、Logging 服務(wù)是否正常運(yùn)行。Tacing 和 Metrics 服務(wù)在運(yùn)行過(guò)程中會(huì)產(chǎn)生日志。
深入了解請(qǐng)戳爆你的屏幕:https://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html
近年來(lái),出現(xiàn)了 APM 系統(tǒng),APM 稱為 應(yīng)用程序性能管理系統(tǒng),可以進(jìn)行 軟件性能監(jiān)視和性能分析。APM 是一種 Metrics,但是現(xiàn)在有融合 Tracing 的趨勢(shì)。
回歸正題,分布式追蹤系統(tǒng)(Tracing)有什么用呢?這里可以以 Jaeger 舉例,它可以:
分布式跟蹤信息傳遞
分布式事務(wù)監(jiān)控
服務(wù)依賴性分析
展示跨進(jìn)程調(diào)用鏈
定位問(wèn)題
性能優(yōu)化
Jaeger 需要結(jié)合后端進(jìn)行結(jié)果分析,jaeger 有個(gè) Jaeger UI,但是功能并不多,因此還需要依賴 Metrics 框架從結(jié)果呈現(xiàn)中可視化,以及自定義監(jiān)控、告警規(guī)則,所以很自然 Metrics 可能會(huì)把 Tracing 的事情也做了。
Dapper
Dapper 是 Google 內(nèi)部使用的分布式鏈路追蹤系統(tǒng),并沒(méi)有開源,但是 Google 發(fā)布了一篇 《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》 論文,這篇論文講述了分布式鏈路追蹤的理論和 Dapper 的設(shè)計(jì)思想。
有很多鏈路追蹤系統(tǒng)是基于 Dapper 論文的,例如淘寶的鷹眼、Twitter 的 Zipkin、Uber 開源的 Jaeger,分布式鏈路追蹤標(biāo)準(zhǔn) OpenTracing 等。
論文地址:
https://static.googleusercontent.com/media/research.google.com/en//archive/papers/dapper-2010-1.pdf
譯文:
http://bigbully.github.io/Dapper-translation/
不能訪問(wèn) github.io 的話,可以 clone 倉(cāng)庫(kù)去看?https://github.com/bigbully/Dapper-translation/tree/gh-pages
Dapper 用戶接口:
分布式追蹤系統(tǒng)的實(shí)現(xiàn)
下圖是一個(gè)由用戶 X 請(qǐng)求發(fā)起的,穿過(guò)多個(gè)服務(wù)的分布式系統(tǒng),A、B、C、D、E 表示不同的子系統(tǒng)或處理過(guò)程。
在這個(gè)圖中, A 是前端,B、C 是中間層、D、E 是 C 的后端。這些子系統(tǒng)通過(guò) rpc 協(xié)議連接,例如 gRPC。
一個(gè)簡(jiǎn)單實(shí)用的分布式鏈路追蹤系統(tǒng)的實(shí)現(xiàn),就是對(duì)服務(wù)器上每一次請(qǐng)求以及響應(yīng)收集跟蹤標(biāo)識(shí)符(message identifiers)和時(shí)間戳(timestamped events)。
分布式服務(wù)的跟蹤系統(tǒng)需要記錄在一次特定的請(qǐng)求后系統(tǒng)中完成的所有工作的信息。用戶請(qǐng)求可以是并行的,同一時(shí)間可能有大量的動(dòng)作要處理,一個(gè)請(qǐng)求也會(huì)經(jīng)過(guò)系統(tǒng)中的多個(gè)服務(wù),系統(tǒng)中時(shí)時(shí)刻刻都在產(chǎn)生各種跟蹤信息,必須將一個(gè)請(qǐng)求在不同服務(wù)中產(chǎn)生的追蹤信息關(guān)聯(lián)起來(lái)。
為了將所有記錄條目與一個(gè)給定的發(fā)起者X關(guān)聯(lián)上并記錄所有信息,現(xiàn)在有兩種解決方案,黑盒(black-box)和基于標(biāo)注(annotation-based)的監(jiān)控方案。
黑盒方案:
假定需要跟蹤的除了上述信息之外沒(méi)有額外的信息,這樣使用統(tǒng)計(jì)回歸技術(shù)來(lái)推斷兩者之間的關(guān)系。
基于標(biāo)注的方案:
依賴于應(yīng)用程序或中間件明確地標(biāo)記一個(gè)全局ID,從而連接每一條記錄和發(fā)起者的請(qǐng)求。
優(yōu)缺點(diǎn):
雖然黑盒方案比標(biāo)注方案更輕便,他們需要更多的數(shù)據(jù),以獲得足夠的精度,因?yàn)樗麄円蕾囉诮y(tǒng)計(jì)推論。基于標(biāo)注的方案最主要的缺點(diǎn)是,很明顯,需要代碼植入。在我們的生產(chǎn)環(huán)境中,因?yàn)樗械膽?yīng)用程序都使用相同的線程模型,控制流和 RPC 系統(tǒng),我們發(fā)現(xiàn),可以把代碼植入限制在一個(gè)很小的通用組件庫(kù)中,從而實(shí)現(xiàn)了監(jiān)測(cè)系統(tǒng)的應(yīng)用對(duì)開發(fā)人員是有效地透明。
Dapper 基于標(biāo)注的方案,接下來(lái)我們將介紹 Dapper 中的一些概念知識(shí)。
跟蹤樹和 span
從形式上看,Dapper 跟蹤模型使用的是樹形結(jié)構(gòu),Span 以及 Annotation。
在前面的圖片中,我們可以看到,整個(gè)請(qǐng)求網(wǎng)絡(luò)是一個(gè)樹形結(jié)構(gòu),用戶請(qǐng)求是樹的根節(jié)點(diǎn)。在 Dapper 的跟蹤樹結(jié)構(gòu)中,樹節(jié)點(diǎn)是整個(gè)架構(gòu)的基本單元。
span 稱為跨度,一個(gè)節(jié)點(diǎn)在收到請(qǐng)求以及完成請(qǐng)求的過(guò)程是一個(gè) span,span 記錄了在這個(gè)過(guò)程中產(chǎn)生的各種信息。每個(gè)節(jié)點(diǎn)處理每個(gè)請(qǐng)求時(shí)都會(huì)生成一個(gè)獨(dú)一無(wú)二的的 span id,當(dāng) A -> C -> D 時(shí),多個(gè)連續(xù)的 span 會(huì)產(chǎn)生父子關(guān)系,那么一個(gè) span 除了保存自己的 span id,也需要關(guān)聯(lián)父、子 span id。生成 span id 必須是高性能的,并且能夠明確表示時(shí)間順序,這點(diǎn)在后面介紹 Jaeger 時(shí)會(huì)介紹。
Annotation 譯為注釋,在一個(gè) span 中,可以為 span 添加更多的跟蹤細(xì)節(jié),這些額外的信息可以幫助我們監(jiān)控系統(tǒng)的行為或者幫助調(diào)試問(wèn)題。Annotation 可以添加任意內(nèi)容。
到此為止,簡(jiǎn)單介紹了一些分布式追蹤以及 Dapper 的知識(shí),但是這些不足以嚴(yán)謹(jǐn)?shù)恼f(shuō)明分布式追蹤的知識(shí)和概念,建議讀者有空時(shí)閱讀 Dapper 論文。
要實(shí)現(xiàn) Dapper,還需要代碼埋點(diǎn)、采樣、跟蹤收集等,這里就不再細(xì)談了,后面會(huì)介紹到,讀者也可以看看論文。
Jaeger 和 OpenTracing
OpenTracing
OpenTracing 是與分布式系統(tǒng)無(wú)關(guān)的API和用于分布式跟蹤的工具,它不僅提供了統(tǒng)一標(biāo)準(zhǔn)的 API,還致力于各種工具,幫助開發(fā)者或服務(wù)提供者開發(fā)程序。
OpenTracing 為標(biāo)準(zhǔn) API 提供了接入 SDK,支持這些語(yǔ)言:Go, JavaScript, Java, Python, Ruby, PHP, Objective-C, C++, C#。
當(dāng)然,我們也可以自行根據(jù)通訊協(xié)議,自己封裝 SDK。
讀者可以參考 OpenTracing 文檔:https://opentracing.io/docs/
接下來(lái)我們要一點(diǎn)點(diǎn)弄清楚 OpenTracing 中的一些概念和知識(shí)點(diǎn)。由于 jaeger 是 OpenTracing 最好的實(shí)現(xiàn),因此后面講 Jaeger 就是 Opentracing ,不需要將兩者嚴(yán)格區(qū)分。
Jaeger 結(jié)構(gòu)
首先是 JAEGER 部分,這部分是代碼埋點(diǎn)等流程,在分布式系統(tǒng)中處理,當(dāng)一個(gè)跟蹤完成后,通過(guò) jaeger-agent 將數(shù)據(jù)推送到 jaeger-collector。jaeger-collector 負(fù)責(zé)處理四面八方推送來(lái)的跟蹤信息,然后存儲(chǔ)到后端,可以存儲(chǔ)到 ES、數(shù)據(jù)庫(kù)等。Jaeger-UI 可以將讓用戶在界面上看到這些被分析出來(lái)的跟蹤信息。
OpenTracing API 被封裝成編程語(yǔ)言的 SDK(jaeger-client),例如在 C# 中是 .dll ,Java 是 .jar,應(yīng)用程序代碼通過(guò)調(diào)用 API 實(shí)現(xiàn)代碼埋點(diǎn)。
jaeger-Agent 是一個(gè)監(jiān)聽(tīng)在 UDP 端口上接收 span 數(shù)據(jù)的網(wǎng)絡(luò)守護(hù)進(jìn)程,它會(huì)將數(shù)據(jù)批量發(fā)送給 collector。
【圖片來(lái)源:https://segmentfault.com/a/1190000011636957】
OpenTracing 數(shù)據(jù)模型
在 OpenTracing 中,跟蹤信息被分為 Trace、Span 兩個(gè)核心,它們按照一定的結(jié)構(gòu)存儲(chǔ)跟蹤信息,所以它們是 OpenTracing 中數(shù)據(jù)模型的核心。
Trace 是一次完整的跟蹤,Trace 由多個(gè) Span 組成。下圖是一個(gè) Trace 示例,由 8 個(gè) Span 組成。
[Span A] ←←←(the root span)|+------+------+| |[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)| |[Span D] +---+-------+| |[Span E] [Span F] >>> [Span G] >>> [Span H]↑↑↑(Span G `FollowsFrom` Span F)Tracing:
a?Trace?can be thought of as a directed acyclic graph (DAG) of?Spans。
有點(diǎn)難翻譯,大概意思是 Trace 是多個(gè) Span 組成的有向非循環(huán)圖。
在上面的示例中,一個(gè) Trace 經(jīng)過(guò)了 8 個(gè)服務(wù),A -> C -> F -> G 是有嚴(yán)格順序的,但是從時(shí)間上來(lái)看,B 、C 是可以并行的。為了準(zhǔn)確表示這些 Span 在時(shí)間上的關(guān)系,我們可以用下圖表示:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time[Span A···················································][Span B··············································][Span D··········································][Span C········································][Span E·······] [Span F··] [Span G··] [Span H··]有個(gè)要注意的地方, 并不是 A -> C -> F 表示 A 執(zhí)行結(jié)束,然后 C 開始執(zhí)行,而是 A 執(zhí)行過(guò)程中,依賴 C,而 C 依賴 F。因此,當(dāng) A 依賴 C 的過(guò)程完成后,最終回到 A 繼續(xù)執(zhí)行。所以上圖中 A 的跨度最大。
Span 格式
要深入學(xué)習(xí),就必須先了解 Span,請(qǐng)讀者認(rèn)真對(duì)照下面的圖片和 Json:
json 地址:?https://github.com/whuanle/DistributedTracing/issues/1
后續(xù)將圍繞這張圖片和 Json 來(lái)舉例講述 Span 相關(guān)知識(shí)。
Trace
一個(gè)簡(jiǎn)化的 Trace 如下:
注:不同編程語(yǔ)言的字段名稱有所差異,gRPC 和 Restful API 的格式也有所差異。
"traceID": "790e003e22209ca4","spans":[...],"processes":{...}前面說(shuō)到,在 OpenTracing 中,Trace 是一個(gè)有向非循環(huán)圖,那么 Trace 必定有且只有一個(gè)起點(diǎn)。
這個(gè)起點(diǎn)會(huì)創(chuàng)建一個(gè) Trace 對(duì)象,這個(gè)對(duì)象一開始初始化了 trace id 和 process,trace id 是一個(gè) 32 個(gè)長(zhǎng)度的字符串組成,它是一個(gè)時(shí)間戳,而 process 是起點(diǎn)進(jìn)程所在主機(jī)的信息。
下面筆者來(lái)說(shuō)一些一下 trace id 是怎么生成的。trace id 是 32個(gè)字符串組成,而實(shí)際上只使用了 16 個(gè),因此,下面請(qǐng)以 16 個(gè)字符長(zhǎng)度去理解這個(gè)過(guò)程。
首先獲取當(dāng)前時(shí)間戳,例如獲得?1611467737781059?共 16 個(gè)數(shù)字,單位是微秒,表示時(shí)間 2021-01-24 13:55:37,秒以下的單位這里就不給出了,明白表示時(shí)間就行。
在 C# 中,將當(dāng)前時(shí)間轉(zhuǎn)為這種時(shí)間戳的代碼:
public static long ToTimestamp(DateTime dateTime){DateTime dt1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);return (dateTime.Ticks - dt1970.Ticks)/10;}// 結(jié)果:1611467737781059如果我們直接使用 Guid 生成或者 string 存儲(chǔ),都會(huì)消耗一些性能和內(nèi)存,而使用 long,剛剛好可以表示時(shí)間戳,還可以節(jié)約內(nèi)存。
獲得這個(gè)時(shí)間戳后,要傳輸?shù)?Jaeger Collector,要轉(zhuǎn)為 byet 數(shù)據(jù),為什么要這樣不太清楚,按照要求傳輸就是了。
將 long 轉(zhuǎn)為一個(gè) byte 數(shù)組:
var bytes = BitConverter.GetBytes(time);// 大小端if (BitConverter.IsLittleEndian){Array.Reverse(bytes);}long 占 8 個(gè)字節(jié),每個(gè) byte 值如下:
0x00 0x05 0xb9 0x9f 0x12 0x13 0xd3 0x43然后傳輸?shù)?Jaeger Collector 中,那么獲得的是一串二進(jìn)制,怎么表示為字符串的 trace id?
可以先還原成 long,然后將 long 輸出為 16 進(jìn)制的字符串:
轉(zhuǎn)為字符串(這是C#):
Console.WriteLine(time.ToString("x016"));結(jié)果:
0005b99f1213d343Span id 也是這樣轉(zhuǎn)的,每個(gè) id 因?yàn)榕c時(shí)間戳相關(guān),所以在時(shí)間上是唯一的,生成的字符串也是唯一的。
這就是 trace 中的 trace id 了,而 trace process 是發(fā)起請(qǐng)求的機(jī)器的信息,用 Key-Value 的形式存儲(chǔ)信息,其格式如下:
{"key": "hostname","type": "string","value": "Your-PC"},{"key": "ip","type": "string","value": "172.6.6.6"},{"key": "jaeger.version","type": "string","value": "CSharp-0.4.2.0"}Ttace 中的 trace id 和 process 這里說(shuō)完了,接下來(lái)說(shuō) trace 的 span。
Span
Span 由以下信息組成:
An operation name:操作名稱,必有;
A start timestamp:開始時(shí)間戳,必有;
A finish timestamp:結(jié)束時(shí)間戳,必有;
Span Tags.:Key-Value 形式表示請(qǐng)求的標(biāo)簽,可選;
Span Logs:Key-Value 形式表示,記錄簡(jiǎn)單的、結(jié)構(gòu)化的日志,必須是字符串類型,可選;
SpanContext?:跨度上下文,在不同的 span 中傳遞,建立關(guān)系;
References?t:引用的其它 Span;
span 之間如果是父子關(guān)系,則可以使用 SpanContext 綁定這種關(guān)系。父子關(guān)系有?ChildOf、FollowsFrom?兩種表示,ChildOf?表示 父 Span 在一定程度上依賴子 Span,而?FollowsFrom?表示父 Span 完全不依賴其子Span 的結(jié)果。
一個(gè) Span 的簡(jiǎn)化信息如下(不用理會(huì)字段名稱大小寫):
{"traceID": "790e003e22209ca4","spanID": "4b73f8e8e77fe9dc","flags": 1,"operationName": "print-hello","references": [],"startTime": 1611318628515966,"duration": 259,"tags": [{"key": "internal.span.format","type": "string","value": "proto"}],"logs": [{"timestamp": 1611318628516206,"fields": [{"key": "event","type": "string","value": "WriteLine"}]}] }OpenTracing API
在 OpenTracing API 中,有三個(gè)主要對(duì)象:
Tracer
Span
SpanContext
Tracer可以創(chuàng)建Spans并了解如何跨流程邊界對(duì)它們的元數(shù)據(jù)進(jìn)行Inject(序列化)和Extract(反序列化)。它具有以下功能:
開始一個(gè)新的?Span
Inject一個(gè)SpanContext到一個(gè)載體
Extract一個(gè)SpanContext從載體
由起點(diǎn)進(jìn)程創(chuàng)建一個(gè) Tracer,然后啟動(dòng)進(jìn)程發(fā)起請(qǐng)求,每個(gè)動(dòng)作產(chǎn)生一個(gè) Span,如果有父子關(guān)系,Tracer 可以將它們關(guān)聯(lián)起來(lái)。當(dāng)請(qǐng)求完成后, Tracer 將跟蹤信息推送到 Jaeger-Collector中。
詳細(xì)請(qǐng)查閱文檔:https://opentracing.io/docs/overview/tracers/
SpanContext 是在不同的 Span 中傳遞信息的,SpanContext 包含了簡(jiǎn)單的 Trace id、Span id 等信息。
我們繼續(xù)以下圖作為示例講解。
A 創(chuàng)建一個(gè) Tracer,然后創(chuàng)建一個(gè) Span,代表自己 (A),再創(chuàng)建兩個(gè) Span,分別代表 B、C,然后通過(guò) SpanContext 傳遞一些信息到 B、C;B 和 C 收到 A 的消息后,也創(chuàng)建一個(gè) Tracer ,用來(lái)?Tracer.extract(...)?;其中 B 沒(méi)有后續(xù),可以直接返回結(jié)果;而 C 的 Tracer 繼續(xù)創(chuàng)建兩個(gè) Span,往 D、E 傳遞 SpanContext。
這個(gè)過(guò)程比較復(fù)雜,筆者講不好,建議讀者參與 OpenTracing 的官方文檔。
詳細(xì)的 OpenTracing API,可以通過(guò)編程語(yǔ)言編寫相應(yīng)服務(wù)時(shí),去學(xué)習(xí)各種 API 的使用。
總結(jié)
以上是生活随笔為你收集整理的分布式链路追踪框架的基本实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Magicodes.IE Excel合并
- 下一篇: TIOBE 3月榜单:新功能将加入,C语