原文鏈接:http://lidawn./2018/12/26/distribute-tracing/
起因
最近一直在做分布式鏈路追蹤的調(diào)研和實(shí)踐,整理一下其中的知識(shí)點(diǎn)。
什么是鏈路追蹤
分布式系統(tǒng)變得日趨復(fù)雜,越來(lái)越多的組件開(kāi)始走向分布式化,如微服務(wù)、分布式數(shù)據(jù)庫(kù)、分布式緩存等,使得后臺(tái)服務(wù)構(gòu)成了一種復(fù)雜的分布式網(wǎng)絡(luò)。在服務(wù)能力提升的同時(shí),復(fù)雜的網(wǎng)絡(luò)結(jié)構(gòu)也使問(wèn)題定位更加困難。在一個(gè)請(qǐng)求在經(jīng)過(guò)諸多服務(wù)過(guò)程中,出現(xiàn)了某一個(gè)調(diào)用失敗的情況,查詢(xún)具體的異常由哪一個(gè)服務(wù)引起的就變得十分抓狂,問(wèn)題定位和處理效率是也會(huì)非常低。
分布式鏈路追蹤就是將一次分布式請(qǐng)求還原成調(diào)用鏈路,將一次分布式請(qǐng)求的調(diào)用情況集中展示,比如各個(gè)服務(wù)節(jié)點(diǎn)上的耗時(shí)、請(qǐng)求具體到達(dá)哪臺(tái)機(jī)器上、每個(gè)服務(wù)節(jié)點(diǎn)的請(qǐng)求狀態(tài)等等。
Dapper
目前業(yè)界的鏈路追蹤系統(tǒng),如Twitter的Zipkin,Uber的Jaeger,阿里的鷹眼,美團(tuán)的Mtrace等都基本被啟發(fā)于google發(fā)表的Dapper。 Dapper闡述了分布式系統(tǒng),特別是微服務(wù)架構(gòu)中鏈路追蹤的概念、數(shù)據(jù)表示、埋點(diǎn)、傳遞、收集、存儲(chǔ)與展示等技術(shù)細(xì)節(jié)。
Trace、Span、Annotations
為了實(shí)現(xiàn)鏈路追蹤,dapper提出了trace,span,annotation的概念。
Trace的含義比較直觀,就是鏈路,指一個(gè)請(qǐng)求經(jīng)過(guò)后端所有服務(wù)的路徑,可以用下面樹(shù)狀的圖形表示。每一條鏈路都用一個(gè)全局唯一的traceid來(lái)標(biāo)識(shí)。

Span之間存在著父子關(guān)系,上游的span是下游的父span,例如圖中"frontend.request"會(huì)調(diào)用"backend.dosomething","backend.dosomething"便成為"frontend.request"的子span。

鏈路中的rpc調(diào)用由span來(lái)表示,對(duì)應(yīng)著樹(shù)狀圖中的邊,每個(gè)span由spanid和parentid來(lái)標(biāo)識(shí),spanid在一條鏈路中唯一。
下圖是dapper論文中給出的一個(gè)"hepler.call"調(diào)用的span詳解。
一個(gè)span一般由client和server兩個(gè)部分的信息組成。按照時(shí)間順序來(lái)解釋?zhuān)琧lient節(jié)點(diǎn)(或者是調(diào)用方)首先發(fā)出請(qǐng)求,產(chǎn)生"client send"(cs)事件,緊接著server節(jié)點(diǎn)(或者是提供方)收到請(qǐng)求,產(chǎn)生"server receive"(sr)事件,server處理完成之后回復(fù)給client,產(chǎn)生"server send"事件,最后client收到回復(fù),產(chǎn)生"client receive"事件。
Client與server兩個(gè)節(jié)點(diǎn)的span信息合并成一次完整的調(diào)用,即一個(gè)完整的span。

Dapper中還定義了annotation的概念,用于用戶自定義事件,如圖二中的"foo",用來(lái)輔助定位問(wèn)題。
值得一提的是,zipkin中把cs,cr,ss,sr這幾個(gè)事件稱(chēng)之為annotation,而對(duì)應(yīng)dapper中的annotation在zipkin v1的數(shù)據(jù)模型中被稱(chēng)之為binaryAnnotation。
帶內(nèi)數(shù)據(jù)與帶外數(shù)據(jù)
鏈路信息的還原依賴(lài)于兩種數(shù)據(jù),一種是各個(gè)節(jié)點(diǎn)產(chǎn)生的事件,如cs,ss,稱(chēng)之為帶外數(shù)據(jù),這些數(shù)據(jù)可以由節(jié)點(diǎn)獨(dú)立生成,并且需要集中上報(bào)到存儲(chǔ)端。
另一種數(shù)據(jù)是traceid,spanid,parentid,用來(lái)標(biāo)識(shí)trace,span,以及span在一個(gè)trace中的位置。這些數(shù)據(jù)需要從鏈路的起點(diǎn)一直傳遞到終點(diǎn),稱(chēng)之為帶內(nèi)數(shù)據(jù)。 通過(guò)帶內(nèi)數(shù)據(jù)的傳遞,可以將一個(gè)鏈路的所有過(guò)程串起來(lái);通過(guò)帶外數(shù)據(jù),可以在存儲(chǔ)端分析更多鏈路的細(xì)節(jié)。
采樣
由于每一個(gè)請(qǐng)求就會(huì)生成一個(gè)鏈路,為了減少性能消耗,避免存儲(chǔ)資源的浪費(fèi),dapper并不會(huì)上報(bào)所有的span數(shù)據(jù),而是使用采樣的方式。通過(guò)采集端自適應(yīng)地調(diào)整采樣率,控制span上報(bào)的數(shù)量,可以在發(fā)現(xiàn)性能瓶頸的同時(shí),有效減少性能損耗。采樣率的概念在其他的追蹤系統(tǒng)中也被廣泛使用。在zipkin小節(jié)中將更具體闡述zipkin的采樣機(jī)制。
存儲(chǔ)
鏈路中的span數(shù)據(jù)經(jīng)過(guò)收集和上報(bào)后會(huì)集中存儲(chǔ)在一個(gè)地方,Dapper使用了BigTable數(shù)據(jù)倉(cāng)庫(kù),如下圖所示,由于每種trace的span個(gè)數(shù)不盡相同,使得BigTable稀疏表格布局很適合這種場(chǎng)景,并且分散的span進(jìn)行存儲(chǔ)時(shí)按照traceid和spanid便可以插入到對(duì)應(yīng)的行列中,使得收集程序可以無(wú)需做任何計(jì)算且無(wú)狀態(tài)。同時(shí)鏈路的查詢(xún)也十分方便,即取表中的一行。

Zipkin
Zipkin是dapper的一種開(kāi)源實(shí)現(xiàn),也是業(yè)界做鏈路追蹤系統(tǒng)的一個(gè)重要參考,其系統(tǒng)也可以即插即用。
架構(gòu)
Zipkin的架構(gòu)中包含Reporter,Transport,Colletor,Storage,API,UI幾個(gè)部分。

其中Reporter集成在每個(gè)服務(wù)的代碼中,負(fù)責(zé)Span的生成,帶內(nèi)數(shù)據(jù)(traceid等)的傳遞,帶外數(shù)據(jù)(span)的上報(bào),采樣控制。
Transport部分為帶外數(shù)據(jù)上報(bào)的通道,zipkin支持http和kafka兩種方式。
Colletor負(fù)責(zé)接收帶外數(shù)據(jù),并插入到集中存儲(chǔ)中。
Storage為存儲(chǔ)組件,適配底層的存儲(chǔ)系統(tǒng),zipkin提供默認(rèn)的in-memory存儲(chǔ),并支持Mysql,Cassandra,ElasticSearch存儲(chǔ)系統(tǒng)。
API提供查詢(xún)、分析和上報(bào)鏈路的接口。接口的定義見(jiàn)zipkin-api。
UI用于展示頁(yè)面展示。

Zipkin將Colletor/Storage/API/UI打包為jar包,可以直接下載運(yùn)行。
數(shù)據(jù)模型
這里的數(shù)據(jù)模型為zipkin v2版本的數(shù)據(jù)模型。
Span
trace_id 為16位或32位的hex字符串,id 、parent_id 為16位hex字符串, 如果沒(méi)有父span,parent_id 為空。
kind 標(biāo)識(shí)服務(wù)節(jié)點(diǎn)的類(lèi)型,有通信模型,cs和生產(chǎn)者消費(fèi)者模型。
name 為span的名字,如rpc調(diào)用的名字。
timestamp 為span生成的時(shí)間戳,微秒。
duration 為span的持續(xù)時(shí)間,client端,即為cr-ss的時(shí)間。
local_endpoint 為本地節(jié)點(diǎn)信息,包含節(jié)點(diǎn)名稱(chēng),ip與端口。
remote_endpoint 為遠(yuǎn)端節(jié)點(diǎn)信息。
annotations 為事件列表,每個(gè)事件用事件時(shí)間戳和名字表示。
tags 為用戶自定義的kv信息,如{"user-id":"lidawn"}。
debug 表示是否為調(diào)試,該選項(xiàng)會(huì)無(wú)視采樣概率,使所有span上報(bào)。
shared 這個(gè)字段暫時(shí)沒(méi)有太理解==。
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;
}
帶內(nèi)數(shù)據(jù)與采樣機(jī)制
Zipkin中對(duì)帶內(nèi)數(shù)據(jù)的傳遞有更加詳細(xì)的描述。帶內(nèi)數(shù)據(jù)被稱(chēng)為b3-propagation ,包含TraceId,SpanId,ParentSpanId,Sampled 四個(gè)字段,每個(gè)server在生成span之后會(huì)得到TraceId,SpanId,ParentSpanId,穿遞到下游server之后,下游server可以知道自己接下來(lái)要生成的span屬于哪一條trace,并處在trace的哪一個(gè)位置。
由于帶內(nèi)數(shù)據(jù)涉及到進(jìn)程之間通信,所以一般是由框架來(lái)做帶內(nèi)數(shù)據(jù)傳遞,這樣可以減少代碼的侵入性。如果服務(wù)之間使用http通信,則可以使用X- 開(kāi)頭的自定義http head來(lái)傳遞帶內(nèi)數(shù)據(jù)?;蛘呷鏶rpc框架,使用clientContext機(jī)制在調(diào)用之間傳遞自定義的字段。目前開(kāi)源的zipkin客戶端一般只支持http和grpc兩種方式。
Zipkin的采樣字段Sampled有四種狀態(tài)Defer/Deny/Accept/Debug ,采樣的一個(gè)重要前提是下游要尊重上游的采樣決定,不能隨意更改sampled字段。
Defer代表該span的采樣狀態(tài)還未決定,下游收到該狀態(tài)時(shí)則可以對(duì)sampled字段重新賦值。
Deny代表該span不上報(bào)。
Accept代表span需要上報(bào)。
Debug一般用于開(kāi)發(fā)環(huán)境,強(qiáng)制上報(bào)。
Root_span的sampled字段由系統(tǒng)的采樣率來(lái)決定。如采樣率為50%,則一半的帶內(nèi)數(shù)據(jù)中sampled字段為accept,其他為deny。
數(shù)據(jù)埋點(diǎn)及上報(bào)過(guò)程
根據(jù)zipkin的span定義,模擬一個(gè)簡(jiǎn)單的調(diào)用過(guò)程,分析數(shù)據(jù)埋點(diǎn)和上報(bào)過(guò)程。

- server-1發(fā)起對(duì)server-2的調(diào)用,生成一個(gè)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的請(qǐng)求,并收到trace_id,id,parent_id,sampled信息,生成一個(gè)相同的span,并記錄kind為SERVER,name,timestamp,local_endpoint(server-2)信息。
- server-2發(fā)起對(duì)server-3的調(diào)用,生成一個(gè)新的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的請(qǐng)求,并收到trace_id,id,parent_id,sampled信息,生成一個(gè)相同的span,并記錄kind為SERVER,name,timestamp,local_endpoint(server-3)信息。
- server-3回復(fù)server-2的調(diào)用,記錄duration,并上報(bào)span。
- server-2收到server-3的回復(fù),記錄duration,并上報(bào)span。
- server-2回復(fù)server-1的調(diào)用,記錄duration,并上報(bào)span。
- server-1收到server-2的回復(fù),記錄duration,并上報(bào)span。
整個(gè)過(guò)程中上報(bào)4個(gè)臨時(shí)的span,最終在zipkin中被合并和存儲(chǔ)為兩個(gè)span。
Open-Tracing
由于各種分布式追蹤系統(tǒng)層出不窮,且有著相似的API語(yǔ)法,但各種語(yǔ)言的開(kāi)發(fā)人員依然很難將他們各自的系統(tǒng)和特定的分布式追蹤系統(tǒng)進(jìn)行整合。在這種情況下,OpenTracing規(guī)范出現(xiàn)了。
OpenTracing通過(guò)提供平臺(tái)無(wú)關(guān)、廠商無(wú)關(guān)的API,使得開(kāi)發(fā)人員能夠方便的添加(或更換)追蹤系統(tǒng)的實(shí)現(xiàn)。OpenTracing通過(guò)定義的API,可實(shí)現(xiàn)將監(jiān)控?cái)?shù)據(jù)記錄到一個(gè)可插拔的tracer上。
Opentracing api的定義可以查看中文文檔, 其并沒(méi)有具體的實(shí)現(xiàn)。對(duì)于現(xiàn)有的系統(tǒng),如zipkin適配opentracing,則需要額外基于現(xiàn)有的client編寫(xiě)適配代碼。
以上。
參考
- Zipkin - https://
- Dapper - https://storage./pub-tools-public-publication-data/pdf/36356.pdf
- Jaeger - https://www./
- 鷹眼 - 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-api/#/default/post_spans
- Zipkin-proto - https://github.com/openzipkin/zipkin-api/blob/master/zipkin.proto
- OpenTracing - https://
- OpenTracing中文 - https://wu-sheng./opentracing-io/content/
原文鏈接:http://lidawn./2018/12/26/distribute-tracing/
|