最近在為 TiDB 加一個 tracing 的工具。 雖然 TiDB 已經(jīng)開始使用 OpenTracing 工具了,但是還遠遠不夠。沒有做到全鏈路追蹤,沒法知道某個具體的 query 在那些節(jié)點慢。 在研究過程中,需要熟練掌握 OpenTracing 的概念,也就是這篇 semantic specification。 在這篇文章里規(guī)定了 不同語言間 OpenTracing 需要實現(xiàn)的函數(shù),類型。
因為 OpenTracing 是跨語言的,按照標準實現(xiàn)的時候,需要盡可能的根據(jù)通用的語言概念,而不能局限于某一個具體的語言特性。這也解釋了這篇文檔的必要性。
The OpenTracing 數(shù)據(jù)模型
首先需要闡明的是 Span 和 trace 的概念。 用圖論的觀點來看的話,traces 可以被認為是 spans 的 DAG。也就是說,對個 spans 形成的 DAG 是一個 Traces。
舉例來說,下圖是一個由八個 Spans 形成的一個 Trace。
單個 Trace 中 Span 之間的因果關(guān)系
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]
↑
↑
↑
(Span G `FollowsFrom` Span F)
某些時候, 用時間順序來具象化更讓人理解。下面就是一個例子。
單個 Trace 中 Spans 之間的時間關(guān)系
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每個 Span 包含一些狀態(tài):
- Operation 的 名字(An operation name)
- 開始 ts (A start timestamp)
- 結(jié)束 ts (A finish timestamp)
- 0個或多個以 keys:values 為形式組成的 Span Tags。 key 必須是 string, values 則可以是 strings, bool,numeric types
- 0個或多個 Span logs
- 一個 SpanContext
- 通過 SpanContext 可以指向 0個 或者多個 因果相關(guān)的 Span。
每個 SpanContext 包含以下狀態(tài):
- 任何 OpenTraceing 實現(xiàn)相關(guān)的狀態(tài)(比如 trace 和 span id)都需要被一個跨進程的 Span 所聯(lián)系。
- ?Baggage Items: 跨進程的 key value 對。
References between Spans
一個 Span 可能, 因因果相關(guān),指向0個或者多個其他的 SpanContexts。 目前來說, OpenTracing 僅僅定義了兩種關(guān)系: ChildOf 和 FollowsForm。如同字面上可以猜測到的, ChildOf 將成為當前 Span 的 child 而 FollowsFrom則會成為 parent。 這兩種關(guān)系為 child span 和 parent span 建立了直接因果關(guān)系。
ChildOf 引用: 某個 Span 可以是 ChildOf 的 parent Span。在一個 ChildOf 的引用中, parent Span, 在某種程度上取決于child Span。 下面列舉能形成 ChildOf 關(guān)系的條件:
- server 端的 RPC 的 Span 可能是
ChildOfclient 端 RPC。 - SQL insert 的 Span 可能是
ChildOf一個 ORM save 方法的 Span - 許多做并發(fā)處理工作的 Span 可能都獨立的是
ChildOf一個單獨的 合并這些在截止時間返回工作結(jié)果的 parent Span。
[-Parent Span---------]
[-Child Span----]
[-Parent Span--------------]
[-Child Span A----]
[-Child Span B----]
[-Child Span C----]
[-Child Span D---------------]
[-Child Span E----]
FollowsFrom 引用: 一些 parent Spans 并不依賴 child Span 的結(jié)果。如果是這種情況, 那么我們基于因果關(guān)系上說Child Span FollowsFrom parent Span。這里,有很多唯一的 FollowsFrom 引用的子類別。這些會在以后被更加正式的定義。
這些是有效的,基于時間順序的 FollowFrom 引用:
[-Parent Span-] [-Child Span-]
[-Parent Span--]
[-Child Span-]
[-Parent Span-]
[-Child Span-]
OpenTracing API
在 OpenTracing 有著三個關(guān)鍵的并且相互關(guān)聯(lián)的類型: Tracer, Span, SpanContext。下面,我們來介紹下每種類型的基本行為。 簡單地說,每種行為都會在具體的語言中變?yōu)橐粋€“方法”,though it may actually be a set of related sibling methods due to type overloading and so on.
在不同語言中,對于 “Optional” 參數(shù)的理解是不一樣的。 舉例來說, Go 里 我們會使用 “functional Options”,但是 Java 里可能會使用 builder 模式。
Tracer
Tracer interface 創(chuàng)造 Spans 并且理解 如何 Inject (serialize) and Extract (deserialize) them across process boundaries. Formally, it has the following capabilities:
Start a new Span
要求的參數(shù)
- 一個 大家都能夠理解的 字符串operation name, 并且精確代表了被 Span 做完的工作。 (例如, 一個 RPC 方法名, 一個函數(shù)名, 或者 超大計算任務(wù)中某個子任務(wù)的名字). The operation name 應(yīng)該 可以辨認一個
Span實例的最為一般的 string. 這是說,"get_user"是比"get_user/314159"更好的.
舉例來說,假設(shè)我們需要獲得賬戶(account)信息, 下面是一些對于一個 Span 可能的 operation names:
| Operation Name | 指導(dǎo) |
|---|---|
get |
過于一般 |
get_account/792 |
過于具體 |
get_account |
贊, 并且 account_id=792 會是一個很好的 Span tag
|
可選擇的參數(shù):
- 零個或者多個 references 到相關(guān)的
SpanContexts, 如果可能的話,包括一個對于ChildOf和FollowsFromreference types 簡略 - 一個可選的,顯性的 start timestamp; 如果被忽略,那么當前 walltime 會被默認使用
- 零個或者多個 tags
返回 一個 已經(jīng)開始的 Span 實例 (但是尚未結(jié)束)
Inject a SpanContext into a carrier
要求參數(shù)
- 一個
SpanContext實例 - 一個能告訴
Tracer如何將SpanContext編碼的 格式 描述 (特別的,對于 string 來說,這個不是必須的) - 一個被 format 所指定的 carrier。某個
Tracer實現(xiàn)根據(jù)這個 format 將SpanContext編碼進入這個 carrier 對象
Extract a SpanContext from a carrier
要求參數(shù)
- 一個 format 描述 (一般但不必要是一個 string 常數(shù))。 目的在于告訴某個
Tracer實現(xiàn) 如何將SpanContextcarrier 中解碼。 - 一個 carrier。它的類型是由format 支配的。某個
Tracer實現(xiàn)會依據(jù)這個 format 將SpanContext從這個 carrier 對象解碼。
**返回一個 SpanContext 實例。 當我們想要通過 Tracer 開始一個新的 Span, 這個實例是可以被用來當做一個。
Note: required formats for injection and extraction
injection 和 extraction 都依賴于一個可擴展的 **format 參數(shù)。 這個參數(shù)規(guī)定了關(guān)聯(lián) “carrier” 的類型,同時也描述了一個 SpanContext 是如何被編碼進入這個 carrier 的。 所有下面的 formats 都必須被所有的 Tracer 實現(xiàn)支持:
- Text Map: 一個任意 string-to-string 的 map。 Keys 和 values 都是不受任何限制的字符 。
- HTTP Headers: 一個 string-to-string 的 map。Keys 和 values 需要適配 HTTP headers (a la RFC 7230. 實踐上來說,因為 HTTP headers 存在被野蠻使用的現(xiàn)象,這里強烈推薦 Tracer 實現(xiàn)限定 HTTP headers 密鑰空間(Key Space) 的使用并且保守的將相應(yīng)的值轉(zhuǎn)義。
-
Binary: 一個 (單獨) 任意的 表示 一個
SpanContextbinary blob。
Span
除去 將 Span's SpanContext 取回的函數(shù),下面任何一個函數(shù)都被不會在 Span 結(jié)束后被調(diào)用。
Retrieve the Spans SpanContext
沒有任何參數(shù)。
返回 特定 SpanContext,對于給定的 Span. 返回值可能在 Span結(jié)束后仍被使用。
Overwrite the operation name
要求參數(shù)。
- 新的 operation name。它將取代在這
Span開始時傳入的 operation name。
Finish the Span
可選參數(shù)
-
Span的 finish timestamp;如果沒設(shè)定,那么當前的 walltime 就會被使用。
除去 將 Span's SpanContext 取回的函數(shù),下面任何一個函數(shù)都被不會在 Span 結(jié)束后被調(diào)用。
Set a Span tag
要求參數(shù)
- the tag key, 必須是 string
- The tag value, 只能是 string, boolean, numeric type 中任意一個。
Note that the OpenTracing project documents certain "standard tags" that have prescribed semantic meanings.
Log structured data
要求參數(shù)
- 一個或者多個 key:value 對。 這些 keys 必須是 string; values 可以有任意類型 一些 OpenTracing 實現(xiàn)可能會處理更多的 log values。
可選參數(shù)
- 一個顯性的 timestamp. 如何設(shè)定,這個值一定是在本地開始時間和 span 結(jié)束時間之間。
Note that the OpenTracing project documents certain "standard log keys" which have prescribed semantic meanings.
Set a baggage item
Baggage items 是 key:value 的 string 對。key:value 對適用于給定的 Span, 它的 SpanContext, 以及有著直接或者間接 引用關(guān)系(reference) 的所有的 Spans。也就是說,baggage items 在隨著 trace 一起在頻內(nèi)傳播(propagate in-band along with trace itself)。
對于一個 full-stack OpenTracing 集成來說,Baggage items 可實現(xiàn)強大的功能(例如,來自移動應(yīng)用程序的任意應(yīng)用程序數(shù)據(jù)可以透明地、深度地進入存儲系統(tǒng)中)中,并帶來有一些巨大的成本:請小心駕駛。
謹慎小心地使用此功能。每個鍵和值都被復(fù)制到相關(guān)Span的每個本地和遠程子元素中,并且這會增加很多網(wǎng)絡(luò)和CPU開銷。
要求參數(shù)
- baggage key, 類型為 string
- baggage value, 類型為 string
Get a baggage item
要求參數(shù)
- The baggage key, 類型為 string
Returns the corresponding baggage value, xor some indication that such a value was missing.
SpanContext
SpanContext更像是一個“概念”,而不是通用 OpenTracing 層的有用功能。也就是說,這對于 OpenTracing 實現(xiàn)非常重要,并且確實呈現(xiàn)了一個自己的瘦API。大多數(shù) OpenTracing 用戶只能在啟動新的 Spans 時通過引用*與 SpanContext 進行交互。或者在某些傳輸協(xié)議中注入/提取跟蹤。
在 OpenTracing 中,我們強制 SpanContext 實例是不可變,以避免 Span 完成和引用周圍的復(fù)雜生命周期問題。
Iterate through all baggage items
這取決于語言以不同的方式進行建模,但在語義上,調(diào)用者應(yīng)該能夠在給定'SpanContext`實例的情況下一次遍歷所有的 baggage items。
NoopTracer
所有實現(xiàn) OpenTracing 語言 API 還必須提供某種 “NoopTracer” 實現(xiàn),可用于標記控制 OpenTracing 或為測試注入無害的東西(等等)。在某些情況下(例如Java),“NoopTracer” 可能在它自己的打包 artifact 中
Optional API Elements
某些語言還提供實用程序來在單個進程中傳入一個活動的“ Span” 和/或 “SpanContext”。例如,opentracing-go提供基于 Go 的 'context.Context機制 的 helpers 來設(shè)置和獲取 中的活動Span`。