自從 Google Dapper 的論文發布之后,各大互聯網公司和開源社區開發的分布式鏈路追蹤產品百花齊放,同時也給使用者帶來了一個問題,各個分布式鏈路追蹤產品的 API 並不兼容,如果用戶在各個產品之間進行切換,成本非常高。
而 OpenTracing 就完美的解決了這個問題,OpenTracing 通過提供平台無關、廠商無關的 API,幫助開發人員能夠方便地添加(或更換)追蹤系統。
Trace 簡介
一個 Trace 代表一個事務、請求或是流程在分布式系統中的執行過程。OpenTracing 中的一條 Trace 被認為是一個由多個 Span 組成的有向無環圖( DAG 圖),一個 Span 代表系統中具有開始時間和執行時長的邏輯單元,Span 一般會有一個名稱,一條 Trace 中 Span 是首尾連接的。

上圖展示了分布式系統中一次客戶端請求的全過程,雖然這種可視化圖形對於查看各組件的組合關系是有用的,但是它不能很好顯示組件的調用時間、先后關系、是串行還是並行等信息,如果想要展現更復雜的調用關系,該圖會更加復雜。
如果將此次客戶端請求的處理流程看作一條 Trace,其中每一次調用,無論是 HTTP 調用、RPC 調用、存儲訪問還是我們比較關注的本地方法調用,都可以成為一個 Span,通常如下圖所示:

圖中每個色塊都是一個 Span,我們可以清晰的看到,請求在進入后端 load balancer 之后,首先會調用 authorization 服務處理,然后調用 billing 服務處理,最后執行 resource 服務,其中 container start-up 和 storage allocation 兩步操作是並行執行的。
Span 簡介
Span 代表系統中具有開始時間和執行時長的邏輯單元,Span 之間通過嵌套或者順序排列建立邏輯因果關系。
每個 Span 中可以包含以下的信息:
操作名稱:例如訪問的具體 RPC 服務,訪問的 URL 地址等;
起始時間;
結束時間;
Span Tag:一組鍵值對構成的 Span 標簽集合,其中鍵必須為字符串類型,值可以是字符串、bool 值或者數字;
Span Log:一組 Span 的日志集合;
SpanContext:Trace 的全局上下文信息;
References:Span 之間的引用關系,下面詳細說明 Span 之間的引用關系;
在一個 Trace 中,一個 Span 可以和一個或者多個 Span 間存在因果關系。目前,OpenTracing 定義了 ChildOf 和 FollowsFrom 兩種 Span 之間的引用關系。這兩種引用類型代表了子節點和父節點間的直接因果關系。
ChildOf 關系:一個 Span 可能是一個父級 Span 的孩子,即為 ChildOf 關系。下面這些情況會構成 ChildOf 關系:
-
一個 HTTP 請求之中,被調用的服務端產生的 Span,與發起調用的客戶端產生的 Span,就構成了 ChildOf 關系;
-
一個 SQL Insert 操作的 Span,和 ORM 的 save 方法的 Span 構成 ChildOf 關系。
很明顯,上述 ChildOf 關系中的父級 Span 都要等待子 Span 的返回,子 Span 的執行時間影響了其所在父級 Span 的執行時間,父級 Span 依賴子 Span 的執行結果。除了串行的任務之外,我們的邏輯中還有很多並行的任務,它們對應的 Span 也是並行的,這種情況下一個父級 Span 可以合並所有子 Span 的執行結果並等待所有並行子 Span 結束。
下圖展示了上述兩種 ChildOf 關系 的 Span:

FollowsFrom 關系:在分布式系統中,一些上游系統(父節點)不以任何方式依賴下游系統(子節點)的執行結果,例如,上游系統通過消息隊列向下游系統發送消息。這種情況下,下游系統對應的子 Span 和上游系統對應的父級 Span 之間是 FollowsFrom 關系。下圖展示了一些可能的 FollowsFrom 關系:

下面的示例 Trace 是由 8 個 Span 組成,其中 Span A 和 Span C 之間是 ChildOf 關系,Span F 和 Span G 之間是 FollowsFrom 關系:

Logs 簡介
每個 Span 可以進行多次 Logs 操作,每一次 Logs 操作,都需要帶一個時間戳,以及一個可選的附加信息。在前文搭建的環境中,請求 http://localhost:8000/err 得到的 Trace 中就會通過 Logs 記錄異常堆棧信息,如下圖所示,其中不僅包括異常的堆棧信息,還包括了一些說明性的鍵值對信息:

Tags 簡介
每個 Span 可以有多個鍵值對形式的 Tags,Tags 是沒有時間戳的,只是為 Span 添加一些簡單解釋和補充信息。下圖展示了前文示例中 Tags 的信息:

SpanContext 和 Baggage
SpanContext 表示進程邊界,在跨進調用時需要將一些全局信息,例如,TraceId、當前 SpanId 等信息封裝到 Baggage 中傳遞到另一個進程(下游系統)中。
Baggage 是存儲在 SpanContext 中的一個鍵值對集合。它會在一條 Trace 中全局傳輸,該 Trace 中的所有 Span 都可以獲取到其中的信息。
需要注意的是,由於 Baggage 需要跨進程全局傳輸,就會涉及相關數據的序列化和反序列化操作,如果在 Baggage 中存放過多的數據,就會導致序列化和反序列化操作耗時變長,使整個系統的 RPC 的延遲增加、吞吐量下降。
雖然 Baggage 與 Span Tags 一樣,都是鍵值對集合,但兩者最大區別在於 Span Tags 中的信息不會跨進程傳輸,而 Baggage 需要全局傳輸。因此,OpenTracing 要求實現提供 Inject 和 Extract 兩種操作,SpanContext 可以通過 Inject 操作向 Baggage 中添加鍵值對數據,通過 Extract 從 Baggage 中獲取鍵值對數據。
核心接口語義
OpenTracing 希望各個實現平台能夠根據上述的核心概念來建模實現,不僅如此,OpenTracing 還提供了核心接口的描述,幫助開發人員更好的實現 OpenTracing 規范。
- Span 接口
Span接口必須實現以下的功能:
獲取關聯的 SpanContext:通過 Span 獲取關聯的 SpanContext 對象。
關閉(Finish)Span:完成已經開始的 Span。
添加 Span Tag:為 Span 添加 Tag 鍵值對。
添加 Log:為 Span 增加一個 Log 事件。
添加 Baggage Item:向 Baggage 中添加一組鍵值對。
獲取 Baggage Item:根據 Key 獲取 Baggage 中的元素。
- SpanContext 接口
SpanContext 接口必須實現以下功能,用戶可以通過 Span 實例或者 Tracer 的 Extract 能力獲取 SpanContext 接口實例。
遍歷 Baggage 中全部的 KV。
-
Tracer 接口
-
Tracer 接口必須實現以下功能:
創建 Span:創建新的 Span。
注入 SpanContext:主要是將跨進程調用攜帶的 Baggage 數據記錄到當前 SpanContext 中。
提取 SpanContext ,主要是將當前 SpanContext 中的全局信息提取出來,封裝成 Baggage 用於后續的跨進程調用。
