[分佈式] 調用鏈追蹤

分佈式鏈路追蹤(Distributed Tracing),也叫 分佈式鏈路跟蹤,分佈式跟蹤,分佈式追蹤 等等。

本文使用分佈式Trace來簡稱分佈式鏈路追蹤。

本篇文章只是從大致的角度來闡述什麼是分佈式Trace,以及一個分佈式Trace系統具備哪些要點和特徵。

場景

先從幾個場景來看為什麼需要分佈式Trace

場景1

開發A編寫了一段代碼,代碼依賴了很多的接口。一個調用下去沒出結果,或者超時了,Debug之後發現是接口M掛了,然後找到這個接口M的負責人B,告知B接口掛了。B拉起自己的調用和Debug環境,按照之前傳過來的調用方式重新Debug了一遍自己的接口,發現NND是自己依賴的接口N掛了,然後找到接口N負責人C。C同樣Debug了自己的接口(此處省略一萬個‘怎麼可能呢,你調用參數不對吧’),最終發現是某個空判斷錯誤,修復bug,轉告給B說我們bug修復了,B再轉告給A說,是C那個傻x弄掛了,現在Ok了,你試一下。

就這樣,一個上午就沒了,看著手頭的需求越堆越高,內心是這樣

[分佈式] 調用鏈追蹤

image.jpg

場景2

哪一天系統完成了開發,需要進行性能測試,發現哪些地方調用比較慢,影響了全局。A工程師拉起自己的系統,調用一遍,就彙報給老闆,時間沒啥問題。B工程師拉起自己的系統,調用了一遍,也沒啥問題,同時將結果彙報了給老闆。C工程師這時候發現自己的系統比較慢,debug發現原來是自己依賴的接口慢了,於是找到接口負責人。。balabala,和場景1一樣,弄好了。老闆一一把這些都記錄下來,滿滿的一本子。哪天改了個需求,又重新來一遍,勞民傷財。

解決方案

這兩種場景只是縮影,假設這時候有這樣一種系統,

[分佈式] 調用鏈追蹤

image.jpg

它記錄了所有系統的調用和依賴,以及這些依賴之間的關係和性能。打個比方,一個網頁訪問了應用M,應用M又分別訪問了A,B,C,D四個應用,如下面這樣的結構

[分佈式] 調用鏈追蹤

image.png

那麼在這個系統中就能夠看到,一個網頁Request了一個應用M,花費了多少時間,請求的IP是多少,請求的網絡開銷是多少。應用M執行時間是多久,是否執行成功,訪問A,B,C,D分別花了多少時間,是否成功,返回了什麼內容,測試是否通過。 然後到下一步,A,B,C,D四個應用本次執行的時間是多久,有沒有超時,調用了多少次DB,每次調用花費了多少時間。

作為示例,給出一個阿里鷹眼的trace圖:

[分佈式] 調用鏈追蹤

image.png

trace就猶如一張大的json表,同一層級的數據代表同一層級的應用,越往下代表是對下層某個應用的依賴。從圖中可以很方便的看到每一個應用調用的名稱,調用花費的時間,以及是否成功。

下面這張圖是我們使用微軟的application insights生成的tracing圖

[分佈式] 調用鏈追蹤

image.png

有些敏感數據打上了馬賽克,盡請諒解。不過還是可以清晰的看到應用之間的依賴關係,有處標紅來代表此次調用出現了問題。

有了這個系統,場景1和場景2中的需求就能解決嗎?如果有了分佈式trace,這些場景中的問題又是怎麼解決呢?

對於場景1中的case,開發A發現自己的接口掛了或者比較慢,而且Debug發現並不是自己代碼的錯誤,這時候他找到自己的這一次trace,圖中就會列出來這一次trace的所有依賴和調用,以及各調用之間的關係。A發現,自己調用的鏈路到N接口那裡就斷了,並且調用N接口返回500錯誤,於是A直接和N接口的負責人C聯繫,C立馬修復了錯誤。

在A調用出錯的時候,系統自動檢測出在N接口出錯,系統立馬生成一份錯誤報告發到A和C的郵箱,A拿到報告的時候就直接能夠知道那個環節出錯了,而C拿到報告的時候發現,A在調用我的接口,並且我的接口出錯了。這就是出錯的主動通知。

對於場景2,項目開發完成了,或者有新的pull request merge到主分支了,觸發了自動化測試。測試下來同樣生成一張鏈路分析圖,不管是開發,測試,DBA,還是老闆,很容易從裡面看到哪些應用的響應速度慢了,讀取DB的時間慢了,接口掛了這些參數。再也不用一個一個蒐集評測報告了。加快了持續集成和持續迭代。

[分佈式] 調用鏈追蹤

image.jpg

分佈式Trace關乎到的不僅僅是開發,運維,還有測試,DBA,以及你老闆的工作量。

上面的例子只是一個縮影,如果一個公司內部存在成千上萬個接口調用,到時候接口負責人都找不到的時候,時間成本和溝通成本無法想象。

標準

現有的分佈式Trace基本都是採用了google 的Dapper標準。

google Dapper

Dapper的思想很簡單,就是在每一次調用棧中,使用同一個TraceId將不同的server聯繫起來。

我們使用幾張Dapper的圖來簡單說明下

依賴

首先來一張應用依賴圖

[分佈式] 調用鏈追蹤

image.png

就是這樣一個調用鏈,一個用戶請求了應用A,應用A需要請求應用B和應用C,而應用C需要請求應用D和應用E。

span

Dapper首先要做的就是規定Trace的結構和基本要素,如下圖:

[分佈式] 調用鏈追蹤

image.png

一次單獨的調用鏈也可以稱為一個span,dapper記錄的是span的名稱,以及每個span的ID和父ID,以重建在一次追蹤過程中不同span之間的關係,上圖中一個矩形框就是一個span,前端從發出請求到收到回覆就是一個span。

再細化到一個span的內部,如下圖:

[分佈式] 調用鏈追蹤

image.png

對於一個特定的span,記錄從Start到End,首先經歷了客戶端發送數據,然後server接收數據,然後server執行內部邏輯,這中間可能去訪問另一個應用。執行完了server將數據返回,然後客戶端接收到數據。

一個span的內容就能構成Trace上面的一個基本元素,可以在這個span中埋點打上各種各樣的Trace類型,比如,一般將客戶端發送記錄成依賴(dependency),服務端接收客戶端以及回覆給客戶端這兩個時間統一記錄成請求(request),如果打上這兩種,那麼在運行完這個span之後,日誌庫中就會多出兩條日誌,一條是dependency的日誌,一條是request的日誌。

現在的Trace SDK,都可以進行配置去自動記錄一些事件,比如數據庫調用依賴,http調用依賴,記錄上游的請求等等,也可以自己手動埋點,在需要打上記錄點的地方寫上記錄的代碼即可。

結構

Dapper中給出的是一張這樣的圖


[分佈式] 調用鏈追蹤

image.png

首先各個日誌收集點按照一定的採樣率將日誌寫進數據文件,然後通過管道將這些日誌文件按照一定的traceId排定輸出到BigTable中去。

如果一個系統完成了上面闡述的架構,基本可以構成一個簡單的Trace系統。

traceId和parentId的生成

在整個過程中,TraceId和ParentId的生成至關重要。首先解釋下TraceId和ParentId。TraceId是標識這個調用鏈的Id,整個調用鏈,從瀏覽器開始放完,到A到B到C,一直到調用結束,所有應用在這次調用中擁有同一個TraceId,所以才能把這次調用鏈在一起。

既然知道了這次調用鏈的整個Id,那麼每次查找問題的時候,只要知道某一個調用的TraceId,就能把所有這個Id的調用全部查找出來,能夠清楚的知道本地調用鏈經過了哪些應用,產生了哪些調用。但是還缺一點,那就是鏈。

在java中有種數據結構叫LinkedList,還有種數據結構叫Tree,即通過父節點就能夠知道子節點,或者通過子節點能夠知道父節點是誰(雙向鏈表),那麼我想知道應用A調用了哪些應用,而又有哪些應用調用了應用A,單純從TraceId裡面根本看不出來,必須要指定自己的父節點才行,這就是ParentId的作用。

先來看一張常規的調用圖


[分佈式] 調用鏈追蹤

image.jpg

調用從一個瀏覽器發起,然後進入到微服務框架中,每一個服務都是一個獨立的應用,應用之間通過RPC進行調用。

分佈式trace有兩個要求,1 是所有的一次調用鏈都採用一個traceId,2是能夠記錄這次調用時從哪裡來的。

在這點上不同的產品有不同的實現方式。

可以想象,最簡單的,就是在一開始瀏覽器請求的時候,定義兩個字段(約定好的),比如一個叫TraceId,一個叫ParentId,放到http的header中,傳遞給應用A,應用A解析傳遞過來的字段,就知道了TraceId和ParentId,即知道了本次調用鏈的Id,以及上一個應用的本次節點Id,然後就打上日誌:某某時間應用A收到了一條請求,TraceId是XXX,它的ParentId是XX。

這樣以後在查找問題的時候,先找到這次調用鏈的TraceId,發現有兩個應用記錄了這個Id,一個是前端的瀏覽器端記錄過,一個是應用A記錄過,並且鏈接關係是前端訪問的應用A。

傳遞兩個參數的方式簡潔易懂,這有個不好的地方,就是每次需要傳遞兩個參數,那麼有沒有一種方案能夠將兩個Id合併為一個Id呢?可以的。不同的產品實現是不一樣的。

用上面的圖作一定解析,(上面的圖是阿里的鷹眼使用的Trace架構),首先為第一個發起這個請求的request分配一個根id,即TraceId,就是上面圖中的0,這個0就是整個Trace中的TraceId,然後應用A拿到了這個號,再在這個0後面添加上0.1和0.2分配給A所請求的應用B和C,B跟C拿到0.1和0.2之後,便可以把這個Id作為ParentId,那麼應用B怎麼獲取TraceId呢,很簡單,只要把string split一下,取第一個值就行,這裡取出來就是0. 所以在設計Id組成的時候,不要把分隔符設計進去,不然就不搞混的。

業內實現

  • 開源的 Open Tracing

openTracing是為了解決不同系統之間的兼容性設計的,現在也成為了各個第三方Trace系統的依賴的規範。

  • Twitter的 Zipin
  • 阿里 鷹眼
  • 大眾點評 (Cat)[https://github.com/dianping/cat]

這是開源的產品

  • Microsoft Application insights

寫在後面

這篇文章只是從最簡單的方面去闡述分佈式Trace,甚至連分佈式都沒有涉及。真正搭建一個分佈式Trace,不僅僅需要定義結構,還需要保證日誌的高可用,支持高併發和高性能。

比如阿里的鷹眼架構:

[分佈式] 調用鏈追蹤

image.jpg

使用Storm集收集和分類日誌數據,然後將簡單分析完的數據一方面寫進Hbase供實時查詢,一方面將全量的日誌寫進HDFS,使用hadoop集群對這些數據進行統計計算,經過鷹眼的服務器把數據渲染展示出來。

可以看到,使用高可用的鷹眼中間件,即需要保證日誌的寫入不會丟失,又不會對現有的業務產生性能影響,選擇合適的消息採樣率至關重要。日誌文件收集的數據庫,需要保證在大併發的條件下依然能夠順利寫入和讀取。還需要保證不同的server之間的數據關聯和事務保證。

這一系列的加起來,就是個龐大的系統了


分享到:


相關文章: