分布式鏈路追蹤(Distributed Tracing),也叫 分布式鏈路跟蹤,分布式跟蹤辩涝,分布式追蹤 等等。
本文使用分布式Trace來簡(jiǎn)稱分布式鏈路追蹤勘天。
本篇文章只是從大致的角度來闡述什么是分布式Trace怔揩,以及一個(gè)分布式Trace系統(tǒng)具備哪些要點(diǎn)和特征。
場(chǎng)景
先從幾個(gè)場(chǎng)景來看為什么需要分布式Trace
場(chǎng)景1
開發(fā)A編寫了一段代碼脯丝,代碼依賴了很多的接口商膊。一個(gè)調(diào)用下去沒出結(jié)果,或者超時(shí)了宠进,Debug之后發(fā)現(xiàn)是接口M
掛了晕拆,然后找到這個(gè)接口M
的負(fù)責(zé)人B,告知B接口掛了材蹬。B拉起自己的調(diào)用和Debug環(huán)境实幕,按照之前傳過來的調(diào)用方式重新Debug了一遍自己的接口,發(fā)現(xiàn)NND是自己依賴的接口N
掛了赚导,然后找到接口N
負(fù)責(zé)人C茬缩。C同樣Debug了自己的接口(此處省略一萬個(gè)‘怎么可能呢赤惊,你調(diào)用參數(shù)不對(duì)吧’)吼旧,最終發(fā)現(xiàn)是某個(gè)空判斷錯(cuò)誤,修復(fù)bug未舟,轉(zhuǎn)告給B說我們bug修復(fù)了圈暗,B再轉(zhuǎn)告給A說,是C那個(gè)傻x弄掛了裕膀,現(xiàn)在Ok了员串,你試一下。
就這樣昼扛,一個(gè)上午就沒了寸齐,看著手頭的需求越堆越高,內(nèi)心是這樣
場(chǎng)景2
哪一天系統(tǒng)完成了開發(fā)抄谐,需要進(jìn)行性能測(cè)試渺鹦,發(fā)現(xiàn)哪些地方調(diào)用比較慢,影響了全局蛹含。A工程師拉起自己的系統(tǒng)毅厚,調(diào)用一遍,就匯報(bào)給老板浦箱,時(shí)間沒啥問題吸耿。B工程師拉起自己的系統(tǒng)祠锣,調(diào)用了一遍,也沒啥問題咽安,同時(shí)將結(jié)果匯報(bào)了給老板伴网。C工程師這時(shí)候發(fā)現(xiàn)自己的系統(tǒng)比較慢,debug發(fā)現(xiàn)原來是自己依賴的接口慢了妆棒,于是找到接口負(fù)責(zé)人是偷。。balabala募逞,和場(chǎng)景1一樣蛋铆,弄好了。老板一一把這些都記錄下來放接,滿滿的一本子刺啦。哪天改了個(gè)需求,又重新來一遍纠脾,勞民傷財(cái)玛瘸。
解決方案
這兩種場(chǎng)景只是縮影,假設(shè)這時(shí)候有這樣一種系統(tǒng)苟蹈,
它記錄了所有系統(tǒng)的調(diào)用和依賴糊渊,以及這些依賴之間的關(guān)系和性能。打個(gè)比方慧脱,一個(gè)網(wǎng)頁訪問了應(yīng)用M渺绒,應(yīng)用M又分別訪問了A,B菱鸥,C宗兼,D四個(gè)應(yīng)用,如下面這樣的結(jié)構(gòu)
那么在這個(gè)系統(tǒng)中就能夠看到氮采,一個(gè)網(wǎng)頁Request了一個(gè)應(yīng)用M殷绍,花費(fèi)了多少時(shí)間,請(qǐng)求的IP是多少鹊漠,請(qǐng)求的網(wǎng)絡(luò)開銷是多少主到。應(yīng)用M執(zhí)行時(shí)間是多久,是否執(zhí)行成功躯概,訪問A登钥,B,C楞陷,D分別花了多少時(shí)間怔鳖,是否成功,返回了什么內(nèi)容,測(cè)試是否通過结执。 然后到下一步度陆,A,B献幔,C懂傀,D四個(gè)應(yīng)用本次執(zhí)行的時(shí)間是多久烁挟,有沒有超時(shí)膘侮,調(diào)用了多少次DB适瓦,每次調(diào)用花費(fèi)了多少時(shí)間充坑。
作為示例,給出一個(gè)阿里鷹眼的trace圖:
trace就猶如一張大的json表腥泥,同一層級(jí)的數(shù)據(jù)代表同一層級(jí)的應(yīng)用奏篙,越往下代表是對(duì)下層某個(gè)應(yīng)用的依賴宋光。從圖中可以很方便的看到每一個(gè)應(yīng)用調(diào)用的名稱情连,調(diào)用花費(fèi)的時(shí)間叽粹,以及是否成功。
下面這張圖是我們使用微軟的application insights生成的tracing圖
有些敏感數(shù)據(jù)打上了馬賽克却舀,盡請(qǐng)諒解虫几。不過還是可以清晰的看到應(yīng)用之間的依賴關(guān)系,有處標(biāo)紅來代表此次調(diào)用出現(xiàn)了問題挽拔。
有了這個(gè)系統(tǒng)辆脸,場(chǎng)景1和場(chǎng)景2中的需求就能解決嗎?如果有了分布式trace螃诅,這些場(chǎng)景中的問題又是怎么解決呢啡氢?
對(duì)于場(chǎng)景1中的case,開發(fā)A發(fā)現(xiàn)自己的接口掛了或者比較慢州刽,而且Debug發(fā)現(xiàn)并不是自己代碼的錯(cuò)誤空执,這時(shí)候他找到自己的這一次trace,圖中就會(huì)列出來這一次trace的所有依賴和調(diào)用穗椅,以及各調(diào)用之間的關(guān)系。A發(fā)現(xiàn)奶栖,自己調(diào)用的鏈路到N
接口那里就斷了匹表,并且調(diào)用N
接口返回500錯(cuò)誤,于是A直接和N
接口的負(fù)責(zé)人C聯(lián)系宣鄙,C立馬修復(fù)了錯(cuò)誤袍镀。
在A調(diào)用出錯(cuò)的時(shí)候,系統(tǒng)自動(dòng)檢測(cè)出在N
接口出錯(cuò)冻晤,系統(tǒng)立馬生成一份錯(cuò)誤報(bào)告發(fā)到A和C的郵箱苇羡,A拿到報(bào)告的時(shí)候就直接能夠知道那個(gè)環(huán)節(jié)出錯(cuò)了,而C拿到報(bào)告的時(shí)候發(fā)現(xiàn)鼻弧,A在調(diào)用我的接口设江,并且我的接口出錯(cuò)了锦茁。這就是出錯(cuò)的主動(dòng)通知。
對(duì)于場(chǎng)景2叉存,項(xiàng)目開發(fā)完成了码俩,或者有新的pull request merge到主分支了,觸發(fā)了自動(dòng)化測(cè)試歼捏。測(cè)試下來同樣生成一張鏈路分析圖稿存,不管是開發(fā),測(cè)試瞳秽,DBA瓣履,還是老板,很容易從里面看到哪些應(yīng)用的響應(yīng)速度慢了练俐,讀取DB的時(shí)間慢了拂苹,接口掛了這些參數(shù)。再也不用一個(gè)一個(gè)搜集評(píng)測(cè)報(bào)告了痰洒。加快了持續(xù)集成和持續(xù)迭代瓢棒。
分布式Trace關(guān)乎到的不僅僅是開發(fā),運(yùn)維丘喻,還有測(cè)試脯宿,DBA,以及你老板的工作量泉粉。
上面的例子只是一個(gè)縮影连霉,如果一個(gè)公司內(nèi)部存在成千上萬個(gè)接口調(diào)用,到時(shí)候接口負(fù)責(zé)人都找不到的時(shí)候嗡靡,時(shí)間成本和溝通成本無法想象跺撼。
標(biāo)準(zhǔn)
現(xiàn)有的分布式Trace基本都是采用了google 的Dapper標(biāo)準(zhǔn)。
Dapper的思想很簡(jiǎn)單讨彼,就是在每一次調(diào)用棧中歉井,使用同一個(gè)TraceId將不同的server聯(lián)系起來。
我們使用幾張Dapper的圖來簡(jiǎn)單說明下
依賴
首先來一張應(yīng)用依賴圖
就是這樣一個(gè)調(diào)用鏈哈误,一個(gè)用戶請(qǐng)求了應(yīng)用A哩至,應(yīng)用A需要請(qǐng)求應(yīng)用B和應(yīng)用C,而應(yīng)用C需要請(qǐng)求應(yīng)用D和應(yīng)用E蜜自。
span
Dapper首先要做的就是規(guī)定Trace的結(jié)構(gòu)和基本要素菩貌,如下圖:
一次單獨(dú)的調(diào)用鏈也可以稱為一個(gè)span,dapper記錄的是span的名稱重荠,以及每個(gè)span的ID和父ID箭阶,以重建在一次追蹤過程中不同span之間的關(guān)系,上圖中一個(gè)矩形框就是一個(gè)span,前端從發(fā)出請(qǐng)求到收到回復(fù)就是一個(gè)span仇参。
再細(xì)化到一個(gè)span的內(nèi)部嘹叫,如下圖:
對(duì)于一個(gè)特定的span,記錄從Start到End冈敛,首先經(jīng)歷了客戶端發(fā)送數(shù)據(jù)待笑,然后server接收數(shù)據(jù),然后server執(zhí)行內(nèi)部邏輯抓谴,這中間可能去訪問另一個(gè)應(yīng)用暮蹂。執(zhí)行完了server將數(shù)據(jù)返回,然后客戶端接收到數(shù)據(jù)癌压。
一個(gè)span的內(nèi)容就能構(gòu)成Trace上面的一個(gè)基本元素仰泻,可以在這個(gè)span中埋點(diǎn)打上各種各樣的Trace類型,比如滩届,一般將客戶端發(fā)送記錄成依賴(dependency)集侯,服務(wù)端接收客戶端以及回復(fù)給客戶端這兩個(gè)時(shí)間統(tǒng)一記錄成請(qǐng)求(request),如果打上這兩種帜消,那么在運(yùn)行完這個(gè)span之后棠枉,日志庫(kù)中就會(huì)多出兩條日志,一條是dependency的日志泡挺,一條是request的日志辈讶。
現(xiàn)在的Trace SDK,都可以進(jìn)行配置去自動(dòng)記錄一些事件娄猫,比如數(shù)據(jù)庫(kù)調(diào)用依賴贱除,http調(diào)用依賴,記錄上游的請(qǐng)求等等媳溺,也可以自己手動(dòng)埋點(diǎn)月幌,在需要打上記錄點(diǎn)的地方寫上記錄的代碼即可。
結(jié)構(gòu)
Dapper中給出的是一張這樣的圖
首先各個(gè)日志收集點(diǎn)按照一定的采樣率將日志寫進(jìn)數(shù)據(jù)文件悬蔽,然后通過管道將這些日志文件按照一定的traceId排定輸出到BigTable中去扯躺。
如果一個(gè)系統(tǒng)完成了上面闡述的架構(gòu),基本可以構(gòu)成一個(gè)簡(jiǎn)單的Trace系統(tǒng)屯阀。
traceId和parentId的生成
在整個(gè)過程中缅帘,TraceId和ParentId的生成至關(guān)重要。首先解釋下TraceId
和ParentId
难衰。TraceId
是標(biāo)識(shí)這個(gè)調(diào)用鏈的Id,整個(gè)調(diào)用鏈逗栽,從瀏覽器開始放完盖袭,到A到B到C,一直到調(diào)用結(jié)束,所有應(yīng)用在這次調(diào)用中擁有同一個(gè)TraceId
鳄虱,所以才能把這次調(diào)用鏈在一起弟塞。
既然知道了這次調(diào)用鏈的整個(gè)Id,那么每次查找問題的時(shí)候拙已,只要知道某一個(gè)調(diào)用的TraceId
决记,就能把所有這個(gè)Id的調(diào)用全部查找出來,能夠清楚的知道本地調(diào)用鏈經(jīng)過了哪些應(yīng)用倍踪,產(chǎn)生了哪些調(diào)用系宫。但是還缺一點(diǎn),那就是鏈建车。
在java中有種數(shù)據(jù)結(jié)構(gòu)叫LinkedList
扩借,還有種數(shù)據(jù)結(jié)構(gòu)叫Tree,即通過父節(jié)點(diǎn)就能夠知道子節(jié)點(diǎn)缤至,或者通過子節(jié)點(diǎn)能夠知道父節(jié)點(diǎn)是誰(雙向鏈表)潮罪,那么我想知道應(yīng)用A調(diào)用了哪些應(yīng)用,而又有哪些應(yīng)用調(diào)用了應(yīng)用A领斥,單純從TraceId
里面根本看不出來嫉到,必須要指定自己的父節(jié)點(diǎn)才行,這就是ParentId
的作用月洛。
先來看一張常規(guī)的調(diào)用圖
調(diào)用從一個(gè)瀏覽器發(fā)起何恶,然后進(jìn)入到微服務(wù)框架中,每一個(gè)服務(wù)都是一個(gè)獨(dú)立的應(yīng)用膊存,應(yīng)用之間通過RPC進(jìn)行調(diào)用导而。
分布式trace有兩個(gè)要求,1 是所有的一次調(diào)用鏈都采用一個(gè)traceId隔崎,2是能夠記錄這次調(diào)用時(shí)從哪里來的今艺。
在這點(diǎn)上不同的產(chǎn)品有不同的實(shí)現(xiàn)方式。
可以想象爵卒,最簡(jiǎn)單的虚缎,就是在一開始瀏覽器請(qǐng)求的時(shí)候,定義兩個(gè)字段(約定好的)钓株,比如一個(gè)叫TraceId
实牡,一個(gè)叫ParentId
,放到http的header中轴合,傳遞給應(yīng)用A创坞,應(yīng)用A解析傳遞過來的字段,就知道了TraceId
和ParentId
受葛,即知道了本次調(diào)用鏈的Id题涨,以及上一個(gè)應(yīng)用的本次節(jié)點(diǎn)Id偎谁,然后就打上日志:某某時(shí)間應(yīng)用A收到了一條請(qǐng)求,TraceId
是XXX纲堵,它的ParentId
是XX巡雨。
這樣以后在查找問題的時(shí)候,先找到這次調(diào)用鏈的TraceId席函,發(fā)現(xiàn)有兩個(gè)應(yīng)用記錄了這個(gè)Id铐望,一個(gè)是前端的瀏覽器端記錄過,一個(gè)是應(yīng)用A記錄過茂附,并且鏈接關(guān)系是前端訪問的應(yīng)用A正蛙。
傳遞兩個(gè)參數(shù)的方式簡(jiǎn)潔易懂,這有個(gè)不好的地方何之,就是每次需要傳遞兩個(gè)參數(shù)跟畅,那么有沒有一種方案能夠?qū)蓚€(gè)Id合并為一個(gè)Id呢?可以的溶推。不同的產(chǎn)品實(shí)現(xiàn)是不一樣的徊件。
用上面的圖作一定解析,(上面的圖是阿里的鷹眼使用的Trace架構(gòu))蒜危,首先為第一個(gè)發(fā)起這個(gè)請(qǐng)求的request分配一個(gè)根id虱痕,即TraceId,就是上面圖中的0辐赞,這個(gè)0就是整個(gè)Trace中的TraceId
部翘,然后應(yīng)用A拿到了這個(gè)號(hào),再在這個(gè)0后面添加上0.1和0.2分配給A所請(qǐng)求的應(yīng)用B和C响委,B跟C拿到0.1和0.2之后新思,便可以把這個(gè)Id作為ParentId
,那么應(yīng)用B怎么獲取TraceId
呢赘风,很簡(jiǎn)單夹囚,只要把string split一下,取第一個(gè)值就行邀窃,這里取出來就是0. 所以在設(shè)計(jì)Id組成的時(shí)候荸哟,不要把分隔符設(shè)計(jì)進(jìn)去,不然就不搞混的瞬捕。
業(yè)內(nèi)實(shí)現(xiàn)
- 開源的 Open Tracing
openTracing是為了解決不同系統(tǒng)之間的兼容性設(shè)計(jì)的鞍历,現(xiàn)在也成為了各個(gè)第三方Trace系統(tǒng)的依賴的規(guī)范。
Twitter的 Zipin
阿里 鷹眼
大眾點(diǎn)評(píng) (Cat)[https://github.com/dianping/cat]
這是開源的產(chǎn)品
- Microsoft Application insights
寫在后面
這篇文章只是從最簡(jiǎn)單的方面去闡述分布式Trace肪虎,甚至連分布式都沒有涉及劣砍。真正搭建一個(gè)分布式Trace,不僅僅需要定義結(jié)構(gòu)扇救,還需要保證日志的高可用秆剪,支持高并發(fā)和高性能赊淑。
比如阿里的鷹眼架構(gòu):
使用Storm集收集和分類日志數(shù)據(jù)爵政,然后將簡(jiǎn)單分析完的數(shù)據(jù)一方面寫進(jìn)Hbase供實(shí)時(shí)查詢仅讽,一方面將全量的日志寫進(jìn)HDFS,使用hadoop集群對(duì)這些數(shù)據(jù)進(jìn)行統(tǒng)計(jì)計(jì)算钾挟,經(jīng)過鷹眼的服務(wù)器把數(shù)據(jù)渲染展示出來洁灵。
可以看到,使用高可用的鷹眼中間件掺出,即需要保證日志的寫入不會(huì)丟失徽千,又不會(huì)對(duì)現(xiàn)有的業(yè)務(wù)產(chǎn)生性能影響,選擇合適的消息采樣率至關(guān)重要汤锨。日志文件收集的數(shù)據(jù)庫(kù)双抽,需要保證在大并發(fā)的條件下依然能夠順利寫入和讀取。還需要保證不同的server之間的數(shù)據(jù)關(guān)聯(lián)和事務(wù)保證闲礼。
這一系列的加起來牍汹,就是個(gè)龐大的系統(tǒng)了。