聊聊分布式鏈路追蹤
原文鏈接: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
這個字段暫時沒有太理解==。
message Span {
bytes trace_id = 1;
bytes parent_id = 2;
bytes id = 3;
enum Kind {
SPAN_KIND_UNSPECIFIED = 0;
CLIENT = 1;
SERVER = 2;
PRODUCER = 3;
CONSUMER = 4;
}
Kind kind = 4;
string name = 5;
fixed64 timestamp = 6;
uint64 duration = 7;
Endpoint local_endpoint = 8;
Endpoint remote_endpoint = 9;
repeated Annotation annotations = 10;
map<string, string> tags = 11;
bool debug = 12;
bool shared = 13;
}
message Endpoint {
string service_name = 1;
bytes ipv4 = 2;
bytes ipv6 = 3;
int32 port = 4;
}
message Annotation {
fixed64 timestamp = 1;
string value = 2;
}
帶內數據與采樣機制
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定義,模擬一個簡單的調用過程,分析數據埋點和上報過程。
- server-1發起對server-2的調用,生成一個root_span, 生成trace_id,id,parent_id為空,並記錄kind為CLIENT,name,timestamp,local_endpoint(server-1)信息,並將trace_id,id,parent_id,sampled信息傳遞給server-2。
- server-2收到server-1的請求,並收到trace_id,id,parent_id,sampled信息,生成一個相同的span,並記錄kind為SERVER,name,timestamp,local_endpoint(server-2)信息。
- server-2發起對server-3的調用,生成一個新的span,該span為root_span的子span。 並記錄kind為CLIENT,name,timestamp,local_endpoint(server-2)信息,並將trace_id,id,parent_id,sampled信息傳遞給server-3。
- server-3收到server-2的請求,並收到trace_id,id,parent_id,sampled信息,生成一個相同的span,並記錄kind為SERVER,name,timestamp,local_endpoint(server-3)信息。
- server-3回復server-2的調用,記錄duration,並上報span。
- server-2收到server-3的回復,記錄duration,並上報span。
- server-2回復server-1的調用,記錄duration,並上報span。
- server-1收到server-2的回復,記錄duration,並上報span。
整個過程中上報4個臨時的span,最終在zipkin中被合並和存儲為兩個span。
Open-Tracing
由於各種分布式追蹤系統層出不窮,且有著相似的API語法,但各種語言的開發人員依然很難將他們各自的系統和特定的分布式追蹤系統進行整合。在這種情況下,OpenTracing規範出現了。
OpenTracing通過提供平臺無關、廠商無關的API,使得開發人員能夠方便的添加(或更換)追蹤系統的實現。OpenTracing通過定義的API,可實現將監控數據記錄到一個可插拔的tracer上。
Opentracing api的定義可以查看中文文檔, 其並沒有具體的實現。對於現有的系統,如zipkin適配opentracing,則需要額外基於現有的client編寫適配代碼。
以上。
參考
- Zipkin - https://zipkin.io
- Dapper - https://storage.googleapis.com/pub-tools-public-publication-data/pdf/36356.pdf
- Jaeger - https://www.jaegertracing.io/
- 鷹眼 - https://cn.aliyun.com/aliware/news/monitoringsolution
- Mtrace - https://tech.meituan.com/mt_mtrace.html
- Zipkin-b3-propagation - https://github.com/openzipkin/b3-propagation
- Zipkin-api - https://zipkin.io/zipkin-api/#/default/post_spans
- Zipkin-proto - https://github.com/openzipkin/zipkin-api/blob/master/zipkin.proto
- OpenTracing - https://opentracing.io
- OpenTracing中文 - https://wu-sheng.gitbooks.io/opentracing-io/content/
原文鏈接:http://lidawn.github.io/2018/12/26/distribute-tracing/
聊聊分布式鏈路追蹤