从Zipkin到Jaeger,Uber的分布式追踪之道tchannel
uber 的 tchannel 的模式是更優(yōu)雅的實(shí)現(xiàn)模式
從Zipkin到Jaeger,Uber的分布式追蹤之道
作者|Yuri Shauro
編輯|大愚若智
對于希望監(jiān)視復(fù)雜的微服務(wù)架構(gòu)系統(tǒng)的組織,分布式追蹤正在快速成為一種不可或缺的工具。Uber工程團(tuán)隊(duì)的開源分布式追蹤系統(tǒng)Jaeger自2016年起,在公司內(nèi)部實(shí)現(xiàn)了大范圍的運(yùn)用,已經(jīng)集成于數(shù)百個(gè)微服務(wù)中,目前每秒鐘已經(jīng)可以記錄數(shù)千條追蹤數(shù)據(jù)。新年伊始,我們想向大家介紹一下這一切是如何實(shí)現(xiàn)的,從我們最開始使用現(xiàn)成的解決方案,如Zipkin,到我們從拉取轉(zhuǎn)換為推送架構(gòu)的原因,以及2017年有關(guān)分布式追蹤的發(fā)展計(jì)劃。
從整體式到微服務(wù)架構(gòu)
隨著Uber的業(yè)務(wù)飛速增長,軟件架構(gòu)的復(fù)雜度也與日俱增。大概一年多前,2015年秋季,我們有大約500個(gè)微服務(wù),2017年初這一數(shù)量已增長至超過2000個(gè)。這樣的增幅部分是由于業(yè)務(wù)該功能的增加,例如面向用戶的UberEATS和UberRUSH等功能,以及類似欺詐檢測、數(shù)據(jù)挖掘、地圖處理等內(nèi)部功能的增加。此外隨著我們從大規(guī)模整體式應(yīng)用程序向著分布式微服務(wù)架構(gòu)遷移,也造成了復(fù)雜度的增加。
遷移到微服務(wù)生態(tài)總是會(huì)遇到獨(dú)特的挑戰(zhàn)。例如喪失對系統(tǒng)的能見度,服務(wù)之間開始產(chǎn)生復(fù)雜的交互等。Uber工程團(tuán)隊(duì)很清楚,我們的技術(shù)會(huì)對大家的生活產(chǎn)生直接影響,系統(tǒng)的可靠性至關(guān)重要,但這一切都離不開“可觀測性”這一前提。傳統(tǒng)的監(jiān)視工具,例如度量值和分布式日志依然發(fā)揮著自己的作用,但這類工具往往無法提供跨越不同服務(wù)的能見度。分布式追蹤應(yīng)運(yùn)而生。
Uber最初的追蹤系統(tǒng)
Uber最初廣泛使用的追蹤系統(tǒng)叫做Merckx,這一名稱源自全球速度最快的自行車騎行選手。Merckx很快就幫助我們了解了有關(guān)Uber基于Python的整體式后端的很多問題。我們可以查詢諸如“查找已登錄用戶的請求,并且請求的處理時(shí)間超過2秒鐘,并且使用了某一數(shù)據(jù)庫來處理,并且事務(wù)維持打開狀態(tài)的時(shí)間超過500ms”這樣的問題。所有待查詢的數(shù)據(jù)被組織成樹狀塊,每個(gè)塊代表某一操作或某個(gè)遠(yuǎn)程調(diào)用,這種組織方式類似于OpenTracing API中“Span”這個(gè)概念。用戶可以在Kafka中使用命令行工具針對數(shù)據(jù)流執(zhí)行即席查詢,也可以使用Web界面查看預(yù)定義的摘要,這些信息均從API端點(diǎn)的高級別行為和Celery任務(wù)中摘要匯總而來。
Merckx使用了一種類似于樹狀塊的調(diào)用圖,每個(gè)塊代表應(yīng)用程序中的一個(gè)操作,例如數(shù)據(jù)庫調(diào)用、RPC,甚至庫函數(shù),例如解析JSON。
Merckx的編排調(diào)度可自動(dòng)應(yīng)用于使用Python編寫的一系列基礎(chǔ)架構(gòu)庫,包括HTTP客戶端和服務(wù)器、SQL查詢、Redis調(diào)用,甚至JSON的序列化。這些編排調(diào)度可記錄有關(guān)每次操作的某些性能度量值和元數(shù)據(jù),例如HTTP調(diào)用的URL,或數(shù)據(jù)庫調(diào)用的SQL查詢。此外還能記錄其他信息,例如數(shù)據(jù)庫事務(wù)維持打開狀態(tài)的時(shí)長,訪問了哪些數(shù)據(jù)庫Shard和副本。
Merckx架構(gòu)使用了拉取模式,可從Kafka的指令數(shù)據(jù)中拉取數(shù)據(jù)流。
Merckx最大的不足在于其設(shè)計(jì)主要面向Uber使用整體式API的年代。Merckx缺乏分布式上下文傳播的概念,雖然可以記錄SQL查詢、Redis調(diào)用,甚至對其他服務(wù)的調(diào)用,但無法進(jìn)一步深入。Merckx還有另一個(gè)有趣的局限:因?yàn)镸erckx數(shù)據(jù)存儲(chǔ)在一個(gè)全局線程本地存儲(chǔ)中,諸如數(shù)據(jù)庫事務(wù)追蹤等大量高級功能只能在uWSGI下使用。隨著Uber開始使用Tornado(一種適用于Python服務(wù)的異步應(yīng)用程序框架),線程本地存儲(chǔ)無法體現(xiàn)Tornado的IOLoop中同一個(gè)線程內(nèi)運(yùn)行的大部分并發(fā)請求。我們開始意識(shí)到不借助全局變量或全局狀態(tài),轉(zhuǎn)為通過某種方式保存請求狀態(tài),并進(jìn)行恰當(dāng)?shù)膫鞑サ闹匾浴?/p>
隨后,使用TChannel進(jìn)行追蹤
2015年初,我們開始開發(fā)TChannel,這是一種適用于RPC的網(wǎng)絡(luò)多路復(fù)用和框架協(xié)議。該協(xié)議的設(shè)計(jì)目標(biāo)之一是將類似于Dapper的分布式追蹤能力融入?yún)f(xié)議中,并為其提供最優(yōu)秀的支持。為了實(shí)現(xiàn)這一目標(biāo),TChannel協(xié)議規(guī)范將追蹤字段直接定義到了二進(jìn)制格式中。
spanid:8 parentid:8 traceid:8 traceflags:1
| spanid | int64 | 用于識(shí)別當(dāng)前span |
| parentid | int64 | 前一個(gè)span |
| traceid | int64 | 負(fù)責(zé)分配的原始操作方 |
| traceflags | uint8 | 位標(biāo)志字段 |
追蹤字段作為二進(jìn)制格式的一部分已包含在TChannel協(xié)議規(guī)范中。
除了協(xié)議規(guī)范,我們還發(fā)布了多個(gè)開源客戶端庫,用于以不同語言實(shí)現(xiàn)該協(xié)議。這些庫的設(shè)計(jì)原則之一是讓應(yīng)用程序需要用到的請求上下文這一概念能夠從服務(wù)器端點(diǎn)貫穿至下游的調(diào)用站點(diǎn)。例如在tchannel-go中,讓出站調(diào)用使用JSON進(jìn)行編碼的簽名需要通過第一個(gè)參數(shù)提供上下文:
func (c *Client) Call(ctx Context, method string, arg, resp interface{}) error {..}
Tchannel庫使得應(yīng)用程序開發(fā)者在編寫自己的代碼時(shí)始終將分布式上下文傳播這一概念銘記于心。
通過將所傳輸內(nèi)容以及內(nèi)存中的上下文對象之間的追蹤上下文進(jìn)行安排,并圍繞服務(wù)處理程序和出站調(diào)用創(chuàng)建追蹤Span,客戶端庫內(nèi)建了對分布式追蹤的支持。從內(nèi)部來看,這些Span在格式上與Zipkin追蹤系統(tǒng)幾乎完全相同,也使用了Zipkin所定義的注釋,例如“cs”(Client Send)和“cr”(Client Receive)。Tchannel使用追蹤報(bào)告程序(Reporter)接口將收集到的進(jìn)程外追蹤Span發(fā)送至追蹤系統(tǒng)的后端。該技術(shù)自帶的庫默認(rèn)包含一個(gè)使用Tchannel本身和Hyperbahn實(shí)現(xiàn)的報(bào)告程序以及發(fā)現(xiàn)和路由層,借此將Thrift格式的Span發(fā)送至收集器群集。
Tchannel客戶端庫已經(jīng)比較近似于我們所需要的分布式追蹤系統(tǒng),該客戶端庫提供了下列構(gòu)建塊:
-
追蹤上下文的進(jìn)程間傳播以及帶內(nèi)請求
-
通過編排API記錄追蹤Span
-
追蹤上下文的進(jìn)程內(nèi)傳播
-
將進(jìn)程外追蹤數(shù)據(jù)報(bào)告至追蹤后端所需的格式和機(jī)制
該系統(tǒng)唯獨(dú)缺少了追蹤后端本身。追蹤上下文的傳輸格式和報(bào)表程序使用的默認(rèn)Thrift格式在設(shè)計(jì)上都可以非常簡單直接地將Tchannel與Zipkin后端集成,然而當(dāng)時(shí)只能通過Scribe將Span發(fā)送至Zipkin,而Zipkin只支持使用Cassandra格式的數(shù)據(jù)存儲(chǔ)。此外當(dāng)時(shí)我們對這些技術(shù)沒什么經(jīng)驗(yàn),因此我們開發(fā)了一套后端原型系統(tǒng),并結(jié)合Zipkin UI的一些自定義組件構(gòu)建了一個(gè)完整的追蹤系統(tǒng)。
后端原型系統(tǒng)架構(gòu):Tchannel生成的追蹤記錄推送給自定義收集器、自定義存儲(chǔ),以及開源的Zipkin UI。
分布式追蹤系統(tǒng)在谷歌和Twitter等主要技術(shù)公司獲得的成功意味著這些公司中廣泛使用的RPC框架、Stubby和Finagle是行之有效的。
同理,Tchannel自帶的追蹤能力也是一個(gè)重大的飛躍。我們部署的后端原型系統(tǒng)已經(jīng)開始從數(shù)十種服務(wù)中收集追蹤信息。隨后我們使用Tchannel構(gòu)建了更多服務(wù),但在生產(chǎn)環(huán)境中全面推廣和廣泛使用依然有些困難。該后端原型以及所使用的Riak/Solr存儲(chǔ)系統(tǒng)無法妥善縮放以適應(yīng)Uber的流量,同時(shí)很多查詢功能依然無法與Zipkin UI實(shí)現(xiàn)足夠好的互操作。盡管新構(gòu)建的服務(wù)大量使用了Tchannel,Uber依然有大量服務(wù)尚未在RPC過程中使用Tchannel,實(shí)際上承擔(dān)核心業(yè)務(wù)的大部分服務(wù)都沒有使用Tchannel。這些服務(wù)主要是通過四大編程語言(Node.js、Python、Go和Java)實(shí)現(xiàn)的,在進(jìn)程間通信方面使用了多種不同的框架。這種異構(gòu)的技術(shù)環(huán)境使得Uber在分布式追蹤系統(tǒng)的構(gòu)建方面會(huì)面臨比谷歌和Twitter更嚴(yán)峻的挑戰(zhàn)。
在紐約市構(gòu)建的Jaeger
Uber紐約工程組織始建于2015年上半年,主要包含兩個(gè)團(tuán)隊(duì):基礎(chǔ)架構(gòu)端的Observability以及產(chǎn)品(包括UberEATS和UberRUSH)端的Uber Everything。考慮到分布式追蹤實(shí)際上是一種形式的生產(chǎn)環(huán)境監(jiān)視,因此更適合交由Observability團(tuán)隊(duì)負(fù)責(zé)。
我們組建了分布式追蹤團(tuán)隊(duì),該團(tuán)隊(duì)由兩個(gè)工程師組成,目標(biāo)也有兩個(gè):將現(xiàn)有的原型系統(tǒng)轉(zhuǎn)換為一種可以全局運(yùn)用的生產(chǎn)系統(tǒng),讓分布式追蹤功能可以適用并適應(yīng)Uber的微服務(wù)。我們還需要為這個(gè)項(xiàng)目起一個(gè)開發(fā)代號。為新事物命名實(shí)際上是計(jì)算機(jī)科學(xué)界兩大老大難問題之一,我們花了幾周時(shí)間集思廣益,考慮了追蹤、探測、捕獲等主題,最終決定命名為Jaeger(?yā-g?r),在德語中這個(gè)詞代表獵手或者狩獵過程中的幫手。
紐約團(tuán)隊(duì)在Cassandra群集方面已經(jīng)具備運(yùn)維經(jīng)驗(yàn),該數(shù)據(jù)庫直接為Zipkin后端提供著支持,因此我們決定棄用基于Riak/Solr的原型。為了接受TChannel流量并將數(shù)據(jù)以兼容Zipkin的二進(jìn)制格式存儲(chǔ)在Cassandra中,我們用Go語言重新實(shí)現(xiàn)了收集器。這樣我們就可以無需改動(dòng),直接使用Zipkin的Web和查詢服務(wù),并通過自定義標(biāo)簽獲得了原本不具備的追蹤記錄搜索功能。我們還為每個(gè)收集器構(gòu)建了一套可動(dòng)態(tài)配置的倍增系數(shù)(Multiplication factor),借此將入站流量倍增n次,這主要是為了通過生產(chǎn)數(shù)據(jù)對后端系統(tǒng)進(jìn)行壓力測試。
Jaeger的早期架構(gòu)依然依賴Zipkin UI和Zipkin存儲(chǔ)格式。
第二個(gè)業(yè)務(wù)需求希望讓追蹤功能可以適用于未使用TChannel進(jìn)行RPC的所有現(xiàn)有服務(wù)。隨后幾個(gè)月我們使用Go、Java、Python和Node.js構(gòu)建了客戶端庫,借此未包括HTTP服務(wù)在內(nèi)各類服務(wù)的編排提供支持。盡管Zipkin后端非常著名并且流行,但依然缺乏足夠完善的編排能力,尤其是在Java/Scala生態(tài)系統(tǒng)之外的編排能力。我們考慮過各種開源的編排庫,但這些庫是由不同的人維護(hù)的,無法確保互操作性,并且通常還使用了完全不同的API,大部分還需要使用Scribe或Kafka作為報(bào)表Span的傳輸機(jī)制。因此我們最終決定自行編寫庫,這樣可以通過集成測試更好地保障互操作性,可以支持我們需要的傳輸機(jī)制,更重要的是,可以用不同的語言提供一致的編排API。我們的所有客戶端庫從一開始都可支持OpenTracing API。
在第一版客戶端庫中,我們還增加了另一個(gè)新穎的功能:可以從追蹤后端輪詢采樣策略。當(dāng)某個(gè)服務(wù)收到不包含追蹤元數(shù)據(jù)的請求后,所編排的追蹤功能通常會(huì)為該請求啟動(dòng)一個(gè)新的追蹤,并生成新的隨機(jī)追蹤ID。然而大部分生產(chǎn)追蹤系統(tǒng),尤其是與Uber的縮放能力有關(guān)的系統(tǒng)無法對每個(gè)追蹤進(jìn)行“描繪”(Profile)或?qū)⑵溆涗浽谧约旱拇鎯?chǔ)中。這樣做會(huì)在服務(wù)與后端系統(tǒng)之間產(chǎn)生難以招架的大流量,甚至?xí)确?wù)所處理的實(shí)際業(yè)務(wù)流量大出好幾個(gè)數(shù)量級。我們改為讓大部分追蹤系統(tǒng)只對小比例的追蹤進(jìn)行采樣,并只對采樣的追蹤進(jìn)行“描繪”和記錄。用于進(jìn)行采樣決策的算法被我們稱之為“采樣策略”。采樣策略的例子包括:
-
采樣一切。主要用于測試用途,但生產(chǎn)環(huán)境中使用會(huì)造成難以承受的開銷!
-
基于概率的采樣,按照固定概率對特定追蹤進(jìn)行隨機(jī)采樣。
-
限速采樣,每個(gè)時(shí)間單位對X個(gè)追蹤進(jìn)行采樣。例如可能會(huì)使用漏桶(Leaky bucket)算法的變體。
大部分兼容Zipkin的現(xiàn)有編排庫可支持基于概率的采樣,但需要在初始化過程中對采樣速率進(jìn)行配置。以我們的規(guī)模,這種方式會(huì)造成一些嚴(yán)重的問題:
-
每個(gè)服務(wù)對不同采樣速率對追蹤后端系統(tǒng)整體流量的影響知之甚少。例如,就算服務(wù)本身使用了適度的每秒查詢數(shù)(QPS)速率,也可能調(diào)用扇出(Fanout)因素非常高的其他下游服務(wù),或由于密集編排導(dǎo)致產(chǎn)生大量追蹤Span。
-
對于Uber來說,每天不同時(shí)段的業(yè)務(wù)流量有著明顯規(guī)律,峰值時(shí)期乘客更多。固定不變的采樣概率對非峰值時(shí)刻可能顯得過低,但對峰值時(shí)刻可能顯得過高。
Jaeger客戶端庫的輪詢功能按照設(shè)計(jì)可以解決這些問題。通過將有關(guān)最恰當(dāng)采樣策略的決策轉(zhuǎn)交給追蹤后端系統(tǒng),服務(wù)的開發(fā)者不再需要猜測最適合的采樣速率。而后端可以按照流量模式的變化動(dòng)態(tài)地調(diào)整采樣速率。下方的示意圖顯示了從收集器到客戶端庫的反饋環(huán)路。
第一版客戶端庫依然使用TChannel發(fā)送進(jìn)程外追蹤Span,會(huì)將其直接提交給收集器,因此這些庫需要依賴Hyperbahn進(jìn)行發(fā)現(xiàn)和路由。對于希望在自己的服務(wù)中運(yùn)用追蹤能力的工程師,這種依賴性造成了不必要的摩擦,這樣的摩擦存在于基礎(chǔ)架構(gòu)層面,以及需要在服務(wù)中額外包含的庫等方面,進(jìn)而可能導(dǎo)致依賴性地域。
為了解決這種問題,我們實(shí)現(xiàn)了一種jaeger-agent邊車(Sidecar)進(jìn)程,并將其作為基礎(chǔ)架構(gòu)組件,與負(fù)責(zé)收集度量值的代理一起部署到所有宿主機(jī)上。所有與路由和發(fā)現(xiàn)有關(guān)的依賴項(xiàng)都封裝在這個(gè)jaeger-agent中,此外我們還重新設(shè)計(jì)了客戶端庫,可將追蹤Span報(bào)告給本地UDP端口,并能輪詢本地回環(huán)接口上的代理獲取采樣策略。新的客戶端只需要最基本的網(wǎng)絡(luò)庫。架構(gòu)上的這種變化向著我們先追蹤后采樣的愿景邁出了一大步,我們可以在代理的內(nèi)存中對追蹤記錄進(jìn)行緩沖。
目前的Jaeger架構(gòu):后端組件使用Go語言實(shí)現(xiàn),客戶端庫使用了四種支持OpenTracing標(biāo)準(zhǔn)的語言,一個(gè)基于React的Web前端,以及一個(gè)基于Apache Spark的后處理和聚合數(shù)據(jù)管道。
統(tǒng)包式分布式追蹤
Zipkin UI是我們在Jaeger中使用的最后一個(gè)第三方軟件。由于要將Span以Zipkin Thrift格式存儲(chǔ)在Cassandra中并與UI兼容,這對我們的后端和數(shù)據(jù)模型產(chǎn)生了一定的限制。尤其是Zipkin模型不支持OpenTracing標(biāo)準(zhǔn)和我們的客戶端庫中兩個(gè)非常重要的功能:鍵-值日志API,以及用更為通用的有向無環(huán)圖(Directed acyclic graph)而非Span樹所代表的追蹤。因此我們毅然決定徹底革新后端所用的數(shù)據(jù)模型,并編寫新的UI。如下圖所示,新的數(shù)據(jù)模型可原生支持鍵-值日志和Span的引用,此外還對發(fā)送到進(jìn)程外的數(shù)據(jù)量進(jìn)行了優(yōu)化,避免進(jìn)程標(biāo)簽在每個(gè)Span上重復(fù):
Jaeger數(shù)據(jù)模型可原生支持鍵-值日志和Span引用。
目前我們正在將后端管道全面升級到新的數(shù)據(jù)模型,以及全新的,更為優(yōu)化的Cassandra架構(gòu)。為了充分利用新的數(shù)據(jù)模型,我們還用Go語言實(shí)現(xiàn)了一個(gè)全新的Jaeger查詢服務(wù),并用React實(shí)現(xiàn)了一套全新的Web UI。最初版本的UI主要重現(xiàn)了Zipkin UI的原有功能,但在設(shè)計(jì)上更易于通過擴(kuò)展提供新的功能和組件,并能作為React組件嵌入到其他UI。例如,用戶可以選擇用多種不同視圖對追蹤結(jié)果進(jìn)行可視化,例如追蹤時(shí)段內(nèi)的直方圖,或服務(wù)在追蹤過程中的累積時(shí)間:
Jaeger UI顯示的追蹤信息搜索結(jié)果。右上角顯示的時(shí)刻和持續(xù)時(shí)間散點(diǎn)圖用可視化方式呈現(xiàn)了結(jié)果,并提供了向下挖掘能力。
另一個(gè)例子,可以根據(jù)不同用例查看同一條追蹤記錄。除了使用默認(rèn)的時(shí)序渲染方式,還可以通過其他視圖渲染為有向無環(huán)圖或關(guān)鍵路徑圖:
Jaeger UI顯示了一條追蹤記錄的詳情。界面頂部是一條追蹤記錄的迷你地圖示意圖,借此可在更大規(guī)模的追蹤記錄中進(jìn)行更輕松的導(dǎo)航。
通過將架構(gòu)中剩余的Zipkin組件替代為Jaeger自己的組件,我們將Jaeger徹底變?yōu)橐环N統(tǒng)包式的端到端分布式追蹤系統(tǒng)。
我們認(rèn)為編排庫是Jaeger固有的一部分,這樣可以確保與Jaeger后端的兼容性,以及通過持續(xù)集成測試保障相互之間的互操作性。(Zipkin生態(tài)系統(tǒng)做不到這些。)尤其是跨越所有可支持語言(目前支持Go、Java、Python和Node.js)和可支持的傳輸方式(目前支持HTTP和TChannel)實(shí)現(xiàn)的互操作性會(huì)在每個(gè)Pull請求中測試,并用到了Uber工程部門RPC團(tuán)隊(duì)所開發(fā)的Crossdock框架。Jaeger客戶端集成測試的詳細(xì)信息請參閱jaeger-client-go crossdock代碼庫。目前所有Jaeger客戶端庫都已開源:
-
Go
-
Java
-
Node.js
-
Python
我們正在將后端和UI代碼遷移至GitHub,并計(jì)劃盡快將Jaeger的源代碼全部公開。如果你對這個(gè)過程感興趣,可以關(guān)注主代碼庫。我們歡迎大家為此做貢獻(xiàn),也很樂于看到更多人嘗試使用Jaeger。雖然我們對目前的進(jìn)展很滿意,但Uber的分布式追蹤工作還有很長的路要走。
Yuri Shkuro是Uber紐約工程部辦公室的全職軟件工程師,目前正全力從事Jaeger和其他Uber工程團(tuán)隊(duì)開源項(xiàng)目。
來源:https://blog.csdn.net/fei33423/article/details/79452948
總結(jié)
以上是生活随笔為你收集整理的从Zipkin到Jaeger,Uber的分布式追踪之道tchannel的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 工行融e借怎么还款等额本息什么意思
- 下一篇: 正川股份是做什么的