分布式系統的運維挑戰
容器、Serverless 編程方式的誕生極大提升了軟件交付與部署的效率。在架構的演化過程中,可以看到兩個變化:
- 應用架構開始從單體系統逐步轉變為微服務,其中的業務邏輯隨之而來就會變成微服務之間的調用與請求。
- 資源角度來看,傳統服務器這個物理單位也逐漸淡化,變成了看不見摸不到的虛擬資源模式。
從以上兩個變化可以看到這種彈性、標准化的架構背后,原先運維與診斷的需求也變得越來越復雜。為了應對這種變化趨勢,誕生一系列面向 DevOps 的診斷與分析系統,包括集中式日志系統(Logging),集中式度量系統(Metrics)和分布式追蹤系統(Tracing)。
Logging,Metrics 和 Tracing
Logging,Metrics 和 Tracing 有各自專注的部分。
- Logging - 用於記錄離散的事件。例如,應用程序的調試信息或錯誤信息。它是我們診斷問題的依據。
- Metrics - 用於記錄可聚合的數據。例如,隊列的當前深度可被定義為一個度量值,在元素入隊或出隊時被更新;HTTP 請求個數可被定義為一個計數器,新請求到來時進行累加。
- Tracing - 用於記錄請求范圍內的信息。例如,一次遠程方法調用的執行過程和耗時。它是我們排查系統性能問題的利器。
這三者也有相互重疊的部分,如下圖所示。
通過上述信息,我們可以對已有系統進行分類。例如,Zipkin 專注於 tracing 領域;Prometheus 開始專注於 metrics,隨着時間推移可能會集成更多的 tracing 功能,但不太可能深入 logging 領域; ELK,阿里雲日志服務這樣的系統開始專注於 logging 領域,但同時也不斷地集成其他領域的特性到系統中來,正向上圖中的圓心靠近。
關於三者關系的更詳細信息可參考 Metrics, tracing, and logging。下面我們重點介紹下 tracing。
Tracing 的誕生
Tracing 是在90年代就已出現的技術。但真正讓該領域流行起來的還是源於 Google 的一篇論文"Dapper, a Large-Scale Distributed Systems Tracing Infrastructure",而另一篇論文"Uncertainty in Aggregate Estimates from Sampled Distributed Traces"中則包含關於采樣的更詳細分析。論文發表后一批優秀的 Tracing 軟件孕育而生,比較流行的有:
- Dapper(Google) : 各 tracer 的基礎
- StackDriver Trace (Google)
- Zipkin(twitter)
- Appdash(golang)
- 鷹眼(taobao)
- 諦聽(盤古,阿里雲雲產品使用的Trace系統)
- 雲圖(螞蟻Trace系統)
- sTrace(神馬)
- X-ray(aws)
分布式追蹤系統發展很快,種類繁多,但核心步驟一般有三個:代碼埋點,數據存儲、查詢展示。
下圖是一個分布式調用的例子,客戶端發起請求,請求首先到達負載均衡器,接着經過認證服務,計費服務,然后請求資源,最后返回結果。
數據被采集存儲后,分布式追蹤系統一般會選擇使用包含時間軸的時序圖來呈現這個 Trace。
但在數據采集過程中,由於需要侵入用戶代碼,並且不同系統的 API 並不兼容,這就導致了如果您希望切換追蹤系統,往往會帶來較大改動。
OpenTracing
為了解決不同的分布式追蹤系統 API 不兼容的問題,誕生了 OpenTracing 規范。
OpenTracing 是一個輕量級的標准化層,它位於應用程序/類庫和追蹤或日志分析程序之間。
+-------------+ +---------+ +----------+ +------------+
| Application | | Library | | OSS | | RPC/IPC | | Code | | Code | | Services | | Frameworks | +-------------+ +---------+ +----------+ +------------+ | | | | | | | | v v v v +------------------------------------------------------+ | OpenTracing | +------------------------------------------------------+ | | | | | | | | v v v v +-----------+ +-------------+ +-------------+ +-----------+ | Tracing | | Logging | | Metrics | | Tracing | | System A | | Framework B | | Framework C | | System D | +-----------+ +-------------+ +-------------+ +-----------+
OpenTracing 的優勢
- OpenTracing 已進入 CNCF,正在為全球的分布式追蹤,提供統一的概念和數據標准。
- OpenTracing 通過提供平台無關、廠商無關的 API,使得開發人員能夠方便的添加(或更換)追蹤系統的實現。
OpenTracing 數據模型
OpenTracing 中的 Trace(調用鏈)通過歸屬於此調用鏈的 Span 來隱性的定義。
特別說明,一條 Trace(調用鏈)可以被認為是一個由多個 Span 組成的有向無環圖(DAG圖),Span 與 Span 的關系被命名為 References。
例如:下面的示例 Trace 就是由8個 Span 組成:
單個 Trace 中,span 間的因果關系 [Span A] ←←←(the root span) | +------+------+ | | [Span B] [Span C] ←←←(Span C 是 Span A 的孩子節點, ChildOf) | | [Span D] +---+-------+ | | [Span E] [Span F] >>> [Span G] >>> [Span H] ↑ ↑ ↑ (Span G 在 Span F 后被調用, FollowsFrom)
有些時候,使用下面這種,基於時間軸的時序圖可以更好的展現 Trace(調用鏈):
單個 Trace 中,span 間的時間關系 ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time [Span A···················································] [Span B··············································] [Span D··········································] [Span C········································] [Span E·······] [Span F··] [Span G··] [Span H··]
每個 Span 包含以下的狀態:(譯者注:由於這些狀態會反映在 OpenTracing API 中,所以會保留部分英文說明)
- An operation name,操作名稱
- A start timestamp,起始時間
- A finish timestamp,結束時間
- Span Tag,一組鍵值對構成的 Span 標簽集合。鍵值對中,鍵必須為 string,值可以是字符串,布爾,或者數字類型。
- Span Log,一組 span 的日志集合。
每次 log 操作包含一個鍵值對,以及一個時間戳。
鍵值對中,鍵必須為 string,值可以是任意類型。
但是需要注意,不是所有的支持 OpenTracing 的 Tracer,都需要支持所有的值類型。
- SpanContext,Span 上下文對象 (下面會詳細說明)
- References(Span間關系),相關的零個或者多個 Span(Span 間通過 SpanContext 建立這種關系)
每一個 SpanContext 包含以下狀態:
- 任何一個 OpenTracing 的實現,都需要將當前調用鏈的狀態(例如:trace 和 span 的 id),依賴一個獨特的 Span 去跨進程邊界傳輸
- Baggage Items,Trace 的隨行數據,是一個鍵值對集合,它存在於 trace 中,也需要跨進程邊界傳輸
更多關於 OpenTracing 數據模型的知識,請參考 OpenTracing語義標准。
OpenTracing 實現
這篇文檔列出了所有 OpenTracing 實現。在這些實現中,比較流行的為 Jaeger 和 Zipkin。
Jaeger
Jaeger 是 Uber 推出的一款開源分布式追蹤系統,兼容 OpenTracing API。
Jaeger 架構
如上圖所示,Jaeger 主要由以下幾部分組成。
- Jaeger Client - 為不同語言實現了符合 OpenTracing 標准的 SDK。應用程序通過 API 寫入數據,client library 把 trace 信息按照應用程序指定的采樣策略傳遞給 jaeger-agent。
- Agent - 它是一個監聽在 UDP 端口上接收 span 數據的網絡守護進程,它會將數據批量發送給 collector。它被設計成一個基礎組件,部署到所有的宿主機上。Agent 將 client library 和 collector 解耦,為 client library 屏蔽了路由和發現 collector 的細節。
- Collector - 接收 jaeger-agent 發送來的數據,然后將數據寫入后端存儲。Collector 被設計成無狀態的組件,因此您可以同時運行任意數量的 jaeger-collector。
- Data Store - 后端存儲被設計成一個可插拔的組件,支持將數據寫入 cassandra、elastic search。
- Query - 接收查詢請求,然后從后端存儲系統中檢索 trace 並通過 UI 進行展示。Query 是無狀態的,您可以啟動多個實例,把它們部署在 nginx 這樣的負載均衡器后面。
Jaeger 存在的問題
- 需要架設並維護存儲。
- UI比較薄弱,有一些個性化的分析需求無法快速滿足(例如對比,統計延遲分布等)。
Jaeger on Aliyun Log Service
Jaeger on Aliyun Log Service 是基於 Jeager 開發的分布式追蹤系統,支持將采集到的追蹤數據持久化到日志服務中,並通過 Jaeger 的原生接口進行查詢和展示。
優勢
- 原生 Jaeger 僅支持將數據持久化到 cassandra 和 elasticsearch 中,用戶需要自行維護后端存儲系統的穩定性,調節存儲容量。Jaeger on Aliyun Log Service 借助阿里雲日志服務的海量數據處理能力,
- 讓您享受 Jaeger 在分布式追蹤領域給您帶來便捷的同時無需過多關注后端存儲系統的問題。
- Jaeger UI 部分僅提供查詢、展示 trace 的功能,對分析問題、排查問題支持不足。使用 Jaeger on Aliyun Log Service,您可以借助日志服務強大的查詢分析能力,助您更快分析出系統中存在的問題。
- 相對於 Jaeger 使用 elasticsearch 作為后端存儲,使用日志服務的好處是支持按量付費,成本僅為 elasticsearch 的13%。參閱自建ELK vs 日志服務(SLS)全方位對比
配置步驟
參閱:https://github.com/aliyun/jaeger/blob/master/README_CN.md
使用實例
HotROD 是由多個微服務組成的應用程序,它使用了 OpenTracing API 記錄 trace 信息。
下面通過一段視頻向您展示如何使用 Jaeger on Aliyun Log Service 診斷 HotROD 出現的問題。視頻包含以下內容:
- 如何配置日志服務
- 如何通過 docker-compose 運行 Jaeger
- 如何運行 HotROD
- 通過 Jaeger UI 如何檢索特定的 trace
- 通過 Jaeger UI 如何查看 trace 的詳細信息
- 通過 Jaeger UI 如何定位應用的性能瓶頸
- 通過日志服務管理控制台,如何定位應用的性能瓶頸
- 應用程序如何使用 OpenTracing API
1. 以分鍾為單位統計 frontend
服務的 HTTP GET /dispatch
操作的平均延遲以及請求個數。
process.serviceName: "frontend" and operationName: "HTTP GET /dispatch" | select from_unixtime( __time__ - __time__ % 60) as time, truncate(avg(duration)/1000/1000) as avg_duration_ms, count(1) as count group by __time__ - __time__ % 60 order by time desc limit 60
2. 比較兩條 trace 各個操作的耗時
traceID: "trace1" or traceID: "trace2" | select operationName, (max(duration)-min(duration))/1000/1000 as duration_diff_ms group by operationName order by duration_diff_ms desc
3. 統計延遲大於 1.5s 的 trace 的 IP 情況
process.serviceName: "frontend" and operationName: "HTTP GET /dispatch" and duration > 1500000000 | select "process.tags.ip" as IP, truncate(avg(duration)/1000/1000) as avg_duration_ms, count(1) as count group by "process.tags.ip"
參考資料
- Jaeger on Aliyun Log Service - https://github.com/aliyun/jaeger
- OpenTracing 中文文檔 - https://wu-sheng.gitbooks.io/opentracing-io/content/
- Jaeger - http://jaeger.readthedocs.io/en/latest/getting_started/
- OpenTracing tutorial - https://github.com/yurishkuro/opentracing-tutorial
- http://peter.bourgon.org/blog/2017/02/21/metrics-tracing-and-logging.html
轉載自:https://developer.aliyun.com/article/514488