需求背景
在一個分布式系統中,一個業務操作往往需要協調多個節點來完成。例如最簡單的查詢使用者資料的操作也會涉及到應用服務,資料庫服務,緩存服務三個節點。由于每個節點上記錄和輸出的資訊若不具備統一标準,則很難準跟蹤現業務操作在各節點上的執行情況。而操作資料跟蹤在分布式系統,特别是微服務架構的分布式系統中尤為重要,它可以幫助業務維護人員:
- 還原生産環境的業務問題現場
- 找出性能瓶頸
- 監控業務健康度
- 識别網絡問題,令分布式系統具備可觀測性
- 輸出結構化資料供大資料系統分析
提供跟蹤服務的開發商可以有很多,為了令使用者可以友善在不同的服務提供商間切換,特别是目前雲原生成為大趨勢的的情況下,能夠降低對具體服務提供商的依賴,降低使用者的切換成本和風險。OpenTracing在此背景下應運而生。
分布式會話跟蹤的困境
在一個分布式系統中,實作完整的會話跟蹤是具有相當挑戰的,因為跟蹤器必須在程序内和程序間傳播跟蹤上下文,這涉及到應用系統的每個部分,特别是如下這些:
- 自包含的開源軟體服務(nginx,Cassandra,redis……)
- 引入開源軟體包的自定義服務(grpc,ORM……)
- 基于上述開源軟體包的各種應用膠水和業務邏輯
要求所有的開源軟體和應用程式使用單一的跟蹤服務提供商是不可能的,如果不過你共享相同的跟蹤描述和傳播機制,則業務調用鍊就會支離破碎。我們需要單一的标準機制來描述系統行為。
為什麼使用OpenTracing
- OpenTracing為解決上述困境提供了開發商獨立的标準,其中包括
- 事務操作(Span)管理:提供程式設計API管理操作的生命周期(開始,結束),記錄操作時間。
- 跨程序的上下文傳播:提供程式設計API在程序間傳播跟蹤上下文。
- 活躍事務操作管理:提供API,在單一程序記憶體儲和擷取跨越軟體包邊界的活躍操作。
- 标準化的上下分和跟蹤資料編碼:提供精确的格式規範用于編碼同構或異構系統間的跟蹤資料。
- OpenTracing是開發商獨立的,且已有衆多開源軟體內建支援
- OpenTracing API本身提供了各種開發語言的版本( Go , JavaScript Java Python Ruby PHP Objective-C C++ C# )
- 第三方OpenTracing API貢獻庫 中提供了各種開源軟體的支援(nginx,Spring,Reactor,RxJava,Kafka……)支援相當廣泛
OpenTracing語義規範
資料模型
在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。