作者:大栗子
原文:http://lidawn.github.io/2018/12/26/distribute-tracing/

起因

最近一直在做分佈式鏈路追蹤的調研和實踐,整理一下其中的知識點。

什麼是鏈路追蹤

分佈式系統變得日趨複雜,越來越多的組件開始走向分佈式化,如微服務、分佈式數據庫、分佈式緩存等,使得後臺服務構成了一種複雜的分佈式網絡。在服務能力提升的同時,複雜的網絡結構也使問題定位更加困難。在一個請求在經過諸多服務過程中,出現了某一個調用失敗的情況,查詢具體的異常由哪一個服務引起的就變得十分抓狂,問題定位和處理效率是也會非常低。

分佈式鏈路追蹤就是將一次分佈式請求還原成調用鏈路,將一次分佈式請求的調用情況集中展示,比如各個服務節點上的耗時、請求具體到達哪臺機器上、每個服務節點的請求狀態等等。

Dapper

目前業界的鏈路追蹤系統,如Twitter的Zipkin,Uber的Jaeger,阿里的鷹眼,美團的Mtrace等都基本被啓發於google發表的Dapper。 Dapper闡述了分佈式系統,特別是微服務架構中鏈路追蹤的概念、數據表示、埋點、傳遞、收集、存儲與展示等技術細節。

Trace、Span、Annotations

爲了實現鏈路追蹤,dapper提出了trace,span,annotation的概念。

Trace的含義比較直觀,就是鏈路,指一個請求經過後端所有服務的路徑,可以用下面樹狀的圖形表示。每一條鏈路都用一個全局唯一的traceid來標識。

分佈式鏈路追蹤系統原來是這麼一回事


Span之間存在着父子關係,上游的span是下游的父span,例如圖中”frontend.request”會調用”backend.dosomething”,”backend.dosomething”便成爲”frontend.request”的子span。

分佈式鏈路追蹤系統原來是這麼一回事


鏈路中的rpc調用由span來表示,對應着樹狀圖中的邊,每個span由spanid和parentid來標識,spanid在一條鏈路中唯一。

下圖是dapper論文中給出的一個”hepler.call”調用的span詳解。

一個span一般由client和server兩個部分的信息組成。按照時間順序來解釋,client節點(或者是調用方)首先發出請求,產生”client send”(cs)事件,緊接着server節點(或者是提供方)收到請求,產生”server receive”(sr)事件,server處理完成之後回覆給client,產生”server send”事件,最後client收到回覆,產生”client receive”事件。

Client與server兩個節點的span信息合併成一次完整的調用,即一個完整的span。

分佈式鏈路追蹤系統原來是這麼一回事


Dapper中還定義了annotation的概念,用於用戶自定義事件,如圖二中的”foo”,用來輔助定位問題。

值得一提的是,zipkin中把cs,cr,ss,sr這幾個事件稱之爲annotation,而對應dapper中的annotation在zipkin v1的數據模型中被稱之爲binaryAnnotation。

帶內數據與帶外數據

鏈路信息的還原依賴於兩種數據,一種是各個節點產生的事件,如cs,ss,稱之爲帶外數據,這些數據可以由節點獨立生成,並且需要集中上報到存儲端。

另一種數據是traceid,spanid,parentid,用來標識trace,span,以及span在一個trace中的位置。這些數據需要從鏈路的起點一直傳遞到終點,稱之爲帶內數據。 通過帶內數據的傳遞,可以將一個鏈路的所有過程串起來;通過帶外數據,可以在存儲端分析更多鏈路的細節。

採樣

由於每一個請求就會生成一個鏈路,爲了減少性能消耗,避免存儲資源的浪費,dapper並不會上報所有的span數據,而是使用採樣的方式。通過採集端自適應地調整採樣率,控制span上報的數量,可以在發現性能瓶頸的同時,有效減少性能損耗。採樣率的概念在其他的追蹤系統中也被廣泛使用。在zipkin小節中將更具體闡述zipkin的採樣機制。

存儲

鏈路中的span數據經過收集和上報後會集中存儲在一個地方,Dapper使用了BigTable數據倉庫,如下圖所示,由於每種trace的span個數不盡相同,使得BigTable稀疏表格佈局很適合這種場景,並且分散的span進行存儲時按照traceid和spanid便可以插入到對應的行列中,使得收集程序可以無需做任何計算且無狀態。同時鏈路的查詢也十分方便,即取表中的一行。

分佈式鏈路追蹤系統原來是這麼一回事


Zipkin

Zipkin是dapper的一種開源實現,也是業界做鏈路追蹤系統的一個重要參考,其系統也可以即插即用。

架構

Zipkin的架構中包含Reporter,Transport,Colletor,Storage,API,UI幾個部分。

分佈式鏈路追蹤系統原來是這麼一回事


其中Reporter集成在每個服務的代碼中,負責Span的生成,帶內數據(traceid等)的傳遞,帶外數據(span)的上報,採樣控制。Transport部分爲帶外數據上報的通道,zipkin支持http和kafka兩種方式。Colletor負責接收帶外數據,並插入到集中存儲中。Storage爲存儲組件,適配底層的存儲系統,zipkin提供默認的in-memory存儲,並支持Mysql,Cassandra,ElasticSearch存儲系統。API提供查詢、分析和上報鏈路的接口。接口的定義見zipkin-api。UI用於展示頁面展示。

分佈式鏈路追蹤系統原來是這麼一回事


Zipkin將Colletor/Storage/API/UI打包爲jar包,可以直接下載運行。

數據模型

這裏的數據模型爲zipkin v2版本的數據模型。

Span

trace_id爲16位或32位的hex字符串, id、 parent_id爲16位hex字符串, 如果沒有父span, parent_id爲空。 kind標識服務節點的類型,有通信模型,cs和生產者消費者模型。 name爲span的名字,如rpc調用的名字。 timestamp爲span生成的時間戳,微秒。 duration爲span的持續時間,client端,即爲cr-ss的時間。 local_endpoint爲本地節點信息,包含節點名稱,ip與端口。 remote_endpoint爲遠端節點信息。 annotations爲事件列表,每個事件用事件時間戳和名字表示。 tags爲用戶自定義的kv信息,如{“user-id”:”lidawn”}。 debug表示是否爲調試,該選項會無視採樣概率,使所有span上報。 shared這個字段暫時沒有太理解==。

分佈式鏈路追蹤系統原來是這麼一回事

帶內數據與採樣機制

Zipkin中對帶內數據的傳遞有更加詳細的描述。帶內數據被稱爲 b3-propagation,包含 TraceId,SpanId,ParentSpanId,Sampled四個字段,每個server在生成span之後會得到TraceId,SpanId,ParentSpanId,穿遞到下游server之後,下游server可以知道自己接下來要生成的span屬於哪一條trace,並處在trace的哪一個位置。

由於帶內數據涉及到進程之間通信,所以一般是由框架來做帶內數據傳遞,這樣可以減少代碼的侵入性。如果服務之間使用http通信,則可以使用 X-開頭的自定義http head來傳遞帶內數據。或者如grpc框架,使用clientContext機制在調用之間傳遞自定義的字段。目前開源的zipkin客戶端一般只支持http和grpc兩種方式。

Zipkin的採樣字段Sampled有四種狀態 Defer/Deny/Accept/Debug,採樣的一個重要前提是下游要尊重上游的採樣決定,不能隨意更改sampled字段。

Defer代表該span的採樣狀態還未決定,下游收到該狀態時則可以對sampled字段重新賦值。

Deny代表該span不上報。

Accept代表span需要上報。

Debug一般用於開發環境,強制上報。

Root_span的sampled字段由系統的採樣率來決定。如採樣率爲50%,則一半的帶內數據中sampled字段爲accept,其他爲deny。

數據埋點及上報過程

根據zipkin的span定義,模擬一個簡單的調用過程,分析數據埋點和上報過程。

分佈式鏈路追蹤系統原來是這麼一回事


  1. server-1發起對server-2的調用,生成一個rootspan, 生成traceid,id,parentid爲空,並記錄kind爲CLIENT,name,timestamp,localendpoint(server-1)信息,並將traceid,id,parentid,sampled信息傳遞給server-2。
  2. server-2收到server-1的請求,並收到traceid,id,parentid,sampled信息,生成一個相同的span,並記錄kind爲SERVER,name,timestamp,local_endpoint(server-2)信息。
  3. server-2發起對server-3的調用,生成一個新的span,該span爲rootspan的子span。 並記錄kind爲CLIENT,name,timestamp,localendpoint(server-2)信息,並將traceid,id,parentid,sampled信息傳遞給server-3。
  4. server-3收到server-2的請求,並收到traceid,id,parentid,sampled信息,生成一個相同的span,並記錄kind爲SERVER,name,timestamp,local_endpoint(server-3)信息。
  5. server-3回覆server-2的調用,記錄duration,並上報span。
  6. server-2收到server-3的回覆,記錄duration,並上報span。
  7. server-2回覆server-1的調用,記錄duration,並上報span。
  8. server-1收到server-2的回覆,記錄duration,並上報span。

整個過程中上報4個臨時的span,最終在zipkin中被合併和存儲爲兩個span。

Open-Tracing

由於各種分佈式追蹤系統層出不窮,且有着相似的API語法,但各種語言的開發人員依然很難將他們各自的系統和特定的分佈式追蹤系統進行整合。在這種情況下,OpenTracing規範出現了。

OpenTracing通過提供平臺無關、廠商無關的API,使得開發人員能夠方便的添加(或更換)追蹤系統的實現。OpenTracing通過定義的API,可實現將監控數據記錄到一個可插拔的tracer上。

Opentracing api的定義可以查看中文文檔, 其並沒有具體的實現。對於現有的系統,如zipkin適配opentracing,則需要額外基於現有的client編寫適配代碼。

以上。

相关文章