1. 程式人生 > >開放分散式追蹤(OpenTracing)入門與 Jaeger 實現

開放分散式追蹤(OpenTracing)入門與 Jaeger 實現

分散式系統的運維挑戰

容器、Serverless 程式設計方式的誕生極大提升了軟體交付與部署的效率。在架構的演化過程中,可以看到兩個變化:

change.png

  • 應用架構開始從單體系統逐步轉變為微服務,其中的業務邏輯隨之而來就會變成微服務之間的呼叫與請求。
  • 資源角度來看,傳統伺服器這個物理單位也逐漸淡化,變成了看不見摸不到的虛擬資源模式。

從以上兩個變化可以看到這種彈性、標準化的架構背後,原先運維與診斷的需求也變得越來越複雜。為了應對這種變化趨勢,誕生一系列面向 DevOps 的診斷與分析系統,包括集中式日誌系統(Logging),集中式度量系統(Metrics)和分散式追蹤系統(Tracing)。

Logging,Metrics 和 Tracing

Logging,Metrics 和 Tracing 有各自專注的部分。

  • Logging - 用於記錄離散的事件。例如,應用程式的除錯資訊或錯誤資訊。它是我們診斷問題的依據。
  • Metrics - 用於記錄可聚合的資料。例如,佇列的當前深度可被定義為一個度量值,在元素入隊或出隊時被更新;HTTP 請求個數可被定義為一個計數器,新請求到來時進行累加。
  • Tracing - 用於記錄請求範圍內的資訊。例如,一次遠端方法呼叫的執行過程和耗時。它是我們排查系統性能問題的利器。

這三者也有相互重疊的部分,如下圖所示。

logging_metrics_tracing.png

通過上述資訊,我們可以對已有系統進行分類。例如,Zipkin 專注於 tracing 領域;Prometheus 開始專注於 metrics,隨著時間推移可能會整合更多的 tracing 功能,但不太可能深入 logging 領域; ELK,阿里雲日誌服務這樣的系統開始專注於 logging 領域,但同時也不斷地整合其他領域的特性到系統中來,正向上圖中的圓心靠近。

Tracing 的誕生

  • Dapper(Google) : 各 tracer 的基礎
  • StackDriver Trace (Google)
  • Zipkin(twitter)
  • Appdash(golang)
  • 鷹眼(taobao)
  • 諦聽(盤古,阿里云云產品使用的Trace系統)
  • 雲圖(螞蟻Trace系統)
  • sTrace(神馬)
  • X-ray(aws)

分散式追蹤系統發展很快,種類繁多,但核心步驟一般有三個:程式碼埋點,資料儲存、查詢展示。

下圖是一個分散式呼叫的例子,客戶端發起請求,請求首先到達負載均衡器,接著經過認證服務,計費服務,然後請求資源,最後返回結果。

opentracing1.png

資料被採集儲存後,分散式追蹤系統一般會選擇使用包含時間軸的時序圖來呈現這個 Trace。

opentracing2.png

但在資料採集過程中,由於需要侵入使用者程式碼,並且不同系統的 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 架構

architecture-jaeger.png

如上圖所示,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 的原生介面進行查詢和展示。

architecture.png

優勢

  • 原生 Jaeger 僅支援將資料持久化到 cassandra 和 elasticsearch 中,使用者需要自行維護後端儲存系統的穩定性,調節儲存容量。Jaeger on Aliyun Log Service 藉助阿里雲日誌服務的海量資料處理能力,讓您享受 Jaeger 在分散式追蹤領域給您帶來便捷的同時無需過多關注後端儲存系統的問題。
  • Jaeger UI 部分僅提供查詢、展示 trace 的功能,對分析問題、排查問題支援不足。使用 Jaeger on Aliyun Log Service,您可以藉助日誌服務強大的查詢分析能力,助您更快分析出系統中存在的問題。
  • 相對於 Jaeger 使用 elasticsearch 作為後端儲存,使用日誌服務的好處是支援按量付費,成本僅為 elasticsearch 的13%。參閱自建ELK vs 日誌服務(SLS)全方位對比

配置步驟

使用例項

HotROD 是由多個微服務組成的應用程式,它使用了 OpenTracing API 記錄 trace 資訊。

下面通過一段視訊向您展示如何使用 Jaeger on Aliyun Log Service 診斷 HotROD 出現的問題。視訊包含以下內容:

  1. 如何配置日誌服務
  2. 如何通過 docker-compose 執行 Jaeger
  3. 如何執行 HotROD
  4. 通過 Jaeger UI 如何檢索特定的 trace
  5. 通過 Jaeger UI 如何檢視 trace 的詳細資訊
  6. 通過 Jaeger UI 如何定位應用的效能瓶頸
  7. 通過日誌服務管理控制檯,如何定位應用的效能瓶頸
  8. 應用程式如何使用 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 是基於阿里雲MVP @WPH95 在業餘時間工作整理而成,感謝 MVP
的傑出貢獻!