一個完整的分佈式追蹤系統是什麼樣子的
現代分佈式鏈路追蹤公認的起源,是 Google 在 2010 年發表的論文《Dapper : a Large-Scale Distributed Systems Tracing Infrastructure》,這篇論文介紹了 Google 從 2004 年開始使用的分佈式追蹤系統 Dapper 的實現原理。
此後,所有業界有名的追蹤系統,無論是國外 Twitter 的 Zipkin、Naver 的 Pinpoint(Naver 是 Line 的母公司,Pinpoint 的出現其實早於 Dapper 論文的發表,在 Dapper 論文中還提到了 Pinpoint),還是國內阿里的鷹眼、大衆點評的 CAT、個人開源的 SkyWalking(後來進入 Apache 基金會孵化畢業),都受到了 Dapper 論文的直接影響。
那麼,從廣義上講,一個完整的分佈式追蹤系統,應該由數據收集、數據存儲和數據展示三個相對獨立的子系統構成;而從狹義上講,則就只是特指鏈路追蹤數據的收集部分。比如 Spring Cloud Sleuth 就屬於狹義的追蹤系統,通常會搭配 Zipkin 作爲數據展示,搭配 Elasticsearch 作爲數據存儲來組合使用。
而前面提到的那些 Dapper 的徒子徒孫們,就大多都屬於廣義的追蹤系統,它們通常也被稱爲 “APM 系統”(Application Performance Management,應用性能管理)。
追蹤與跨度
爲了有效地進行分佈式追蹤,Dapper 提出了 “追蹤” 與“跨度”兩個概念。
從客戶端發起請求抵達系統的邊界開始,記錄請求流經的每一個服務,直到向客戶端返回響應爲止,這整個過程就叫做一次 “追蹤”(Trace,爲了不產生混淆,我後面就直接使用英文 Trace 來指代了)。
由於每次 Trace 都可能會調用數量不定、座標不定的多個服務,那麼爲了能夠記錄具體調用了哪些服務,以及調用的順序、開始時點、執行時長等信息,每次開始調用服務前,系統都要先埋入一個調用記錄,這個記錄就叫做一個 “跨度”(Span)。
Span 的數據結構應該足夠簡單,以便於能放在日誌或者網絡協議的報文頭裏;也應該足夠完備,起碼要含有時間戳、起止時間、Trace 的 ID、當前 Span 的 ID、父 Span 的 ID 等能夠滿足追蹤需要的信息。
事實上,每一次 Trace 都是由若干個有順序、有層級關係的 Span 所組成一顆 “追蹤樹”(Trace Tree),如下圖所示:
那麼這樣來看,我們就可以從下面兩個角度來觀察分佈式追蹤的特徵:
從目標來看,鏈路追蹤的目的是爲排查故障和分析性能提供數據支持,系統在對外提供服務的過程中,持續地接受請求並處理響應,同時持續地生成 Trace,按次序整理好 Trace 中每一個 Span 所記錄的調用關係,就能繪製出一幅系統的服務調用拓撲圖了。
其實這個用時序圖 (順序圖) 表示會更爲清晰
uml 使用那一片文章寫過開發人員應該掌握的畫圖能力
這樣,根據拓撲圖中 Span 記錄的時間信息和響應結果(正常或異常返回),我們就可以定位到緩慢或者出錯的服務;然後,將 Trace 與歷史記錄進行對比統計,就可以從系統整體層面分析服務性能,定位性能優化的目標。
從實現來看,爲每次服務調用記錄 Trace 和 Span,並以此構成追蹤樹結構,看起來好像也不是很複雜。然而考慮到實際情況,追蹤系統在功能性和非功能性上都有不小的挑戰。
功能上的挑戰來源於服務的異構性,各個服務可能會採用不同的程序語言,服務間的交互也可能會採用不同的網絡協議,每兼容一種場景,都會增加功能實現方面的工作量。
而且想要做成一個成型產品,還要加上每個節點,不同環境請求重試復現。
而非功能性的挑戰,具體就來源於以下這四個方面:
-
低性能損耗:分佈式追蹤不能對服務本身產生明顯的性能負擔。追蹤的主要目的之一就是爲了尋找性能缺陷,越慢的服務就越是需要追蹤,所以工作場景都是性能敏感的地方。
-
隨應用擴縮:現代的分佈式服務集羣都有根據流量壓力自動擴縮的能力,這就要求當業務系統擴縮時,追蹤系統也能自動跟隨,不需要運維人員人工參與。
-
持續的監控:即要求追蹤系統必須能夠 7x24 小時工作,否則就難以定位到系統偶爾抖動的行爲。
所以總而言之,分佈式追蹤的主要需求是如何圍繞着一個服務調用過程中的 Trace 和 Span,來低損耗、高透明度地收集信息,不管是狹義還是廣義的鏈路追蹤系統,都要包含數據收集的工作,這是可以說是追蹤系統的核心。那麼接下來,我們就來了解下三種主流的數據收集方式。
目前,追蹤系統根據數據收集方式的差異,可以分爲三種主流的實現方式,分別是基於日誌的追蹤(Log-Based Tracing),基於服務的追蹤(Service-Based Tracing)和基於邊車代理的追蹤(Sidecar-Based Tracing)。
基於日誌的追蹤
基於日誌的追蹤思路是將 Trace、Span 等信息直接輸出到應用日誌中,然後隨着所有節點的日誌歸集過程匯聚到一起,再從全局日誌信息中反推出完整的調用鏈拓撲關係。日誌追蹤對網絡消息完全沒有侵入性,對應用程序只有很少量的侵入性,對性能的影響也非常低。
但這種實現方式的缺點是直接依賴於日誌歸集過程,日誌本身不追求絕對的連續與一致,這就導致了基於日誌的追蹤,往往不如其他兩種追蹤實現來的精準。
還有一個問題是,由於業務服務的調用與日誌的歸集並不是同時完成的,也通常不由同一個進程完成,有可能發生業務調用已經順利結束了,但由於日誌歸集不及時或者精度丟失,導致日誌出現延遲或缺失記錄,進而產生追蹤失真的情況。這也正是我在上節課介紹 Elastic Stack 時提到的觀點,ELK 在日誌、追蹤和度量方面都可以發揮作用,這對中小型應用確實能起到一定的便利作用,但對於大型系統來說,最好還是由專業的工具來做專業的事。
日誌追蹤的代表產品是 Spring Cloud Sleuth,下面是一段由 Sleuth 在調用時自動生成的日誌記錄,你可以從中觀察到 TraceID、SpanID、父 SpanID 等追蹤信息。
# 以下爲調用端的日誌輸出:
Created new Feign span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]
2019-06-30 09:43:24.022 [http-nio-9010-exec-8] DEBUG o.s.c.s.i.web.client.feign.TraceFeignClient - The modified request equals GET http://localhost:9001/product/findAll HTTP/1.1
X-B3-ParentSpanId: cbe97e67ce162943
X-B3-Sampled: 0
X-B3-TraceId: cbe97e67ce162943
X-Span-Name: http:/product/findAll
X-B3-SpanId: bb1798f7a7c9c142
# 以下爲服務端的日誌輸出:
[findAll] to a span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]
Adding a class tag with value [ProductController] to a span [Trace: cbe97e67ce162943, Span: bb1798f7a7c9c142, Parent: cbe97e67ce162943, exportable:false]
基於服務的追蹤
基於服務的追蹤是目前最爲常見的追蹤實現方式,被 Zipkin、SkyWalking、Pinpoint 等主流追蹤系統廣泛採用。服務追蹤的實現思路是通過某些手段給目標應用注入追蹤探針(Probe),比如針對 Java 應用,一般就是通過 Java Agent 注入的。
探針在結構上可以看作是一個寄生在目標服務身上的小型微服務系統,它一般會有自己專用的服務註冊、心跳檢測等功能,有專門的數據收集協議,可以把從目標系統中監控得到的服務調用信息,通過另一次獨立的 HTTP 或者 RPC 請求,發送給追蹤系統。
因此,基於服務的追蹤會比基於日誌的追蹤消耗更多的資源,也具有更強的侵入性,而換來的收益就是追蹤的精確性與穩定性都有所保證,不必再依靠日誌歸集來傳輸追蹤數據。
這裏我放了一張 Pinpoint 的追蹤效果截圖,從圖中可以看到參數、變量等相當詳細的方法級調用信息。不知道你還記不記得,在上節課 “日誌分析” 裏,其實可以把 “打印追蹤診斷信息” 需要診斷方法參數、返回值、上下文信息,或者方法調用耗時這類數據,通過追蹤系統來實現,會是比通過日誌系統實現更加恰當的解決方案。
另外,我也必須給你說明清楚,像圖例中的 Pinpoint 這種詳細程度的追蹤,對應用系統的性能壓力是相當大的,一般僅在除錯時開啓,而且 Pinpoint 本身就是比較重負載的系統(運行它必須先維護一套 HBase),這其實就嚴重製約了它的適用範圍。
有的公司會選擇在預發環境放置全鏈路追蹤,流量較少性能損失和機器成本可以接受,線上出問題的 case 使用請求參數復現,可以一定程度平衡成本性能和需求。
目前服務追蹤的其中一個發展趨勢是輕量化,國產的 SkyWalking 正是這方面的佼佼者。
基於邊車代理的追蹤
基於邊車代理的追蹤是服務網格的專屬方案 (Service Mesh 跟 k8s 的 pod 一樣自己維護一個代理,負責網絡等與業務無關的部分,業務部分交給 pod,他們整體作爲一個服務提供者對外提供訪問),也是最理想的分佈式追蹤模型,它對應用完全透明,無論是日誌還是服務本身,都不會有任何變化;它與程序語言無關,無論應用是採用什麼編程語言來實現的,只要它還是通過網絡(HTTP 或者 gRPC)來訪問服務,就可以被追蹤到;它也有自己獨立的數據通道,追蹤數據通過控制平面進行上報,避免了追蹤對程序通信或者日誌歸集的依賴和干擾,保證了最佳的精確性。
而如果要說這種追蹤實現方式還有什麼缺點的話,那就是服務網格現在還不夠普及。當然未來隨着雲原生的發展,相信它會成爲追蹤系統的主流實現方式之一。
還有一點就是,邊車代理本身對應用透明的工作原理,決定了它只能實現服務調用層面的追蹤,像前面 Pinpoint 截圖那樣的本地方法調用級別的追蹤診斷,邊車代理是做不到的。(其實一般情況服務間數據就足夠診斷了)
現在,市場佔有率最高的邊車代理 Envoy 就提供了相對完善的追蹤功能,但沒有提供自己的界面端和存儲端,所以 Envoy 和 Sleuth 一樣,都屬於狹義的追蹤系統,需要配合專門的 UI 與存儲來使用。SkyWalking、Zipkin、Jaeger、LightStep Tracing 等系統,現在都可以接受來自於 Envoy 的追蹤數據,充當它的界面端
不過,雖然鏈路追蹤在數據的收集這方面,已經有了幾種主流的實現方式,但各種追蹤產品通常並不互通。接下來我們就具體看看追蹤在行業標準與規範方面存在的問題。
追蹤規範化
CNCF 技術委員會發布了 OpenTracing 和谷歌和微軟推出了 OpenCensus 這兩個競品,2019 年又忽然宣佈握手言和,它們共同發佈了可觀測性的終極解決方案 OpenTelemetry,並宣佈會各自凍結 OpenTracing 和 OpenCensus 的發展。
OpenTelemetry 的野心很大,它不僅包括了追蹤規範,還包括了日誌和度量方面的規範、各種語言的 SDK,以及採集系統的參考實現。距離一個完整的追蹤與度量系統,只是差了一個界面端和指標預警這些會與用戶直接接觸的後端功能,OpenTelemetry“大度” 地把它們留給具體產品去實現,勉強算是沒有對一衆 APM 廠商趕盡殺絕,留了一條活路。
不過,OpenTelemetry 畢竟是 2019 年纔出現的新生事物,儘管背景淵源深厚,前途光明,但未來究竟如何發展,能否打敗現在已有的衆多成熟系統,目前仍然言之尚早。
小結
這節課,我給你介紹了分佈式追蹤裏 “追蹤” 與“跨度”兩個概念,要知道目前幾乎所有的追蹤工具都是圍繞這兩個 Dapper 提出的概念所設計的,因此理解它們的含義,對你使用任何一款追蹤工具都會有幫助。而在理論之外,我還講解了三種追蹤數據收集的實現方式,分別是基於日誌、基於服務、基於邊車代理的追蹤,你可以重點關注下這幾種方式各自的優勢和缺點,以此在工作實踐中選擇合適的追蹤方式。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/cqvPN15JI4fu3Jxz2euEjw