資料模型
在OpenTracing中,會話(Trace)由一組具有引用關系的操作(Span)來表示。
- 會話(Trace):分布式系統中協調各節點程序完成的邏輯事務
- 操作(Span):需要消耗一定時間來完成的計算邏輯單元
一個會話可以視為一組操作的有向無循環圖,操作間的邊被稱為引用(Reference)。例如下圖表示由8個操作構成的會話:
[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)
也可以用時間軸來可視化表示會話,像下圖這樣表示一次會話中操作的連續時間關系:
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每個操作具有如下的狀态:
- 操作名稱
- 開始時間
- 結束時間
- 由0或多個名值對組成的Tags,Key必須為字元串,Value可以是字元串,數值和布爾類型。
- 一個操作上下文(SpanContext)
- 操作間的觸發關系引用,通過互相關聯的操作上下文來表示。
每個操作上下文(SpanContext)封裝如下狀态:
- 跨程序中唯一操作引用的任何與OpenTracing具體實作無關的狀态(例如會話和操作id),也就是說這些狀态在整個系統中表示唯一一次操作。
- 行李(Baggage Items),跨程序傳播的字元串名值對資料。
操作間的引用
一個操作可以可能引用0或多個其他的具有觸發關系的操作上下文。OpenTracing現在定義了兩種類型的引用:ChildOf和FollowsFrom。兩種引用都表示子操作和父操作之間的觸發關系。未來,OpenTracing可能也會支援非觸發關系的引用類型(例如批量聚合在一起的操作,或者在同一個隊列中的操作等等)。
ChildOf引用:一個操作可以是另一個操作的子操作,在一個ChildOf引用中,父操作在一定程度上依賴于子操作。一下場景符合ChildOf關系:
- 一次RPC調用的服務端操作是用戶端操作的子操作
- 一個表示SQL插入的操作是一個ORM save方法的子操作
- 一個父操作可以有多個同步進行(可能是分布式)的父操作,該父操作會在執行期限内聚合所有子操作的結果傳回給使用者
上述場景用時序圖表示如下:
[-Parent Span---------]
[-Child Span----]
[-Parent Span--------------]
[-Child Span A----]
[-Child Span B----]
[-Child Span C----]
[-Child Span D---------------]
[-Child Span E----]
FollowsFrom引用:有些父操作不以任何方式依賴它們的子操作的結果。在這種場景下,子操作僅僅由父操作觸發。符合這種關系的操作可以進一步分成很多子類型,在未來的版本中OpenTracing可能會做進一步的規範區分。
用時序圖表示如下:
[-Parent Span-] [-Child Span-]
[-Parent Span--]
[-Child Span-]
[-Parent Span-]
[-Child Span-]
OpenTracing API
在OpenTracing規範中,有三個關鍵的,相關關聯的類型:Tracer,Span和SpanContext。接下來會詳細說明每種類型的行為;簡單來說,每種行為對應特定程式設計語言中的“方法(method)”,可能是一組相關的兄弟方法或重載方法。
在這裡我們讨論的“可選(optional)”參數,在不同的程式設計語言中有不同的方式來表示該概念。例如在Go中我們可以使用“函數式選項(functional Options)”這種慣用方法,而在Java中可以通過構造器模式(builder pattern)實作。
Tracer
Tracer接口用于建立Span,并且知道如何在跨越程序邊界時Inject(序列化)和Extract(反序列化)它們。Tracer具有如下功能:
- 開始一個新的操作(Span)
必選參數:
-
一個操作名稱,一個人類可讀的字元串,可以簡潔地表示這個操作完成的工作(例如,RPC方法的名稱,函數名,子任務名稱或者一個複雜計算任務中的階段名稱)。操作名稱應當是具有普遍意義的能夠用于識别一類操作(Span)執行個體,而非特指某個操作。也就是說“get_user”比“get_user/314159”更好。
例如,用于擷取帳戶資訊的操作,可以有如下的操作明名稱:
| Operation Name | |
| --------------- | ------------------------------------------ |
| get | 太籠統,不能表達擷取帳戶資訊 |
| get_account/792 | 太具體,表達了擷取指定id=792的帳戶資訊 |
| get_account | 合适,可以用account_id=792作為Span tag |
可選參數:
- 0或多個相關SpanContext的引用(references),如果可能需要包含引用類型(ChildOf或FollowsFrom)
- 一個可選的明确的起始時間戳;如果忽略該參數,則預設使用目前系統時間。
- 0或多個标簽(tags)
傳回一個已經開始但尚未結束的Span對象執行個體。
- 将一個操作上下文(SpanContext)注入到載體(carrier)中
- 操作上下文(SpanContext)執行個體
- 格式描述符(format),通常是一個字元串常量,告知Tracer的實作如何将SpanContext執行個體編碼到載體的參數中
- 載體(carrier),類型根據format的定義而不同。Tracer實作會将SpanContext按照format進行編碼放入載體對象中。
- 從carrier中提取SpanContext
- format:告知Tracer實作如何從carrier參數中解碼SpanContext
- carrier:該參數的類型根據format的定義而不同。Tracer實作會按照format将carrier對象解碼為SpanContext
傳回一個SpanContext執行個體,通過Tracer啟動一個Span的時候可以作為上下文引用。
注意:format參數定義了關聯的載體的類型,同時也定義了如何将SpanContext編碼到載體中。OpenTracing的實作必須支援以下format的注入和提取行為。
- Text Map:載體是任意的字元串映射表(string-to-string map),key和value都不限制字元集。
- HTTP Headers:載體是字元串映射表,key和value使用的字元集必須能夠用于HTTP headers(遵循 RFC 7230 )。實踐中,由于存在非常多樣的HTTP Headers,是以強烈建議Tracer實作使用受限的header key空間,并對value進行保守的轉義。
- Binary:使用獨立的二進制資料塊表示SpanContext
Span
除了用于擷取操作的上下文(SpanContext)的方法外,以下方法均不可在操作結束(Span is finished)後調用。
- 擷取操作上下文(SpanContxt)
沒有參數。
傳回指定操作(Span)的上下文(SpanContext)。即使在操作結束後,傳回值依然可用。
- 覆寫操作名稱
必選參數:新的操作名稱,覆寫Span開始時被賦予的操作名。
- 結束Span
可選參數:明确指定的Span結束時間戳(finish timestamp);如果忽略該參數,則使用目前系統時間。
- 設定Span 标簽(Tag)
- tag key:必須是字元串
- tag value:字元串,數值或布爾類型
注意OpenTracing項目文檔化了一些“标準标簽”,具有預定義的語義。
- 記錄結構化資料
- 一個或多名值對,key必須是字元串,value可以是任意類型。不同的OpenTracing實作可以處理的資料值類型可能不同。
- 一個明确指定的時間戳,如果指定了,時間戳必須在Span開始和結束時間中間。
注意OpenTracing項目文檔定義了一些“辨別日志key”,具備預定義的語義。
- 設定行李(baggage item)
Baggage Item是操作負載的字元串名值對,其作用于Span,SpanContext以及所有的直接或間接引用的本地Span上。也就是說Baggage Items在整個會話中進行傳播。
Baggage items是全棧內建OpenTracing的系統中的非常強大的功能,例如任意的應用資料可以從建立它的移動應用終端透明地傳輸到深入底層的存儲系統中,但該特性的成本也非常高,需要謹慎使用。
深思熟慮後謹慎使用該特性。每個key-value對會被拷貝到所有關聯的本地和遠端Span中,這會給網絡帶寬和CPU帶來很重的負擔。
必須參數:
- baggage key:字元串
- baggage value:字元串
- 擷取行李(baggage item)
傳回對應的baggage value,或某種找不到值的訓示。
SpanContext
在通用OpenTracing層中,SpanContext更多對一種概念,而非一些有用的功能。也就是說它對OpenTracing實作很重要,但是它自身并沒有API表示。大多數的OpenTracing使用者隻會在建立新Span,或從與一些傳輸協定進行inject和extract操作時通過引與SpanContext進行互動。
在OpenTracing中我們強制SpanContext執行個體必須是不可變的(immutable)以避免圍繞Span結束和引用的複雜聲明周期問題。
- 周遊所有的baggage items
依賴于具體的程式設計語言,有不同的程式設計模型實作,但是從語義上來說,一旦使用者得到了一個SpanContext執行個體,即可高效呃周遊所有的baggage items
NoopTracer
OpenTracing API的各種語言實作,必須提供某種NoopTracer實作,該實作不進行任何有意義的操作。可以用于測試或實作OpenTracing開啟關閉的控制。NoopTracer可能在自己的包構件中(例如Java的OpenTracing API構件opentracing-noop)
可選的API元素
一些語言提供了在單個程序中傳遞活躍Span,SpanContext的功能。例如,opentracing-go提供了在Go的context.Context中設定和擷取活躍Span的輔助工具。