最近在为 TiDB 加一个 tracing 的工具。 虽然 TiDB 已经开始使用 OpenTracing 工具了,但是还远远不够。没有做到全链路追踪,没法知道某个具体的 query 在那些节点慢。 在研究过程中,需要熟练掌握 OpenTracing 的概念,也就是这篇 semantic specification。 在这篇文章里规定了 不同语言间 OpenTracing 需要实现的函数,类型。
因为 OpenTracing 是跨语言的,按照标准实现的时候,需要尽可能的根据通用的语言概念,而不能局限于某一个具体的语言特性。这也解释了这篇文档的必要性。
The OpenTracing 数据模型
首先需要阐明的是 Span 和 trace 的概念。 用图论的观点来看的话,traces 可以被认为是 spans 的 DAG。也就是说,对个 spans 形成的 DAG 是一个 Traces。
举例来说,下图是一个由八个 Spans 形成的一个 Trace。
单个 Trace 中 Span 之间的因果关系
[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 之间的时间关系
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每个 Span 包含一些状态:
- Operation 的 名字(An operation name)
- 开始 ts (A start timestamp)
- 结束 ts (A finish timestamp)
- 0个或多个以 keys:values 为形式组成的 Span Tags。 key 必须是 string, values 则可以是 strings, bool,numeric types
- 0个或多个 Span logs
- 一个 SpanContext
- 通过 SpanContext 可以指向 0个 或者多个 因果相关的 Span。
每个 SpanContext 包含以下状态:
- 任何 OpenTraceing 实现相关的状态(比如 trace 和 span id)都需要被一个跨进程的 Span 所联系。
- Baggage Items: 跨进程的 key value 对。
References between Spans
一个 Span 可能, 因因果相关,指向0个或者多个其他的 SpanContexts。 目前来说, OpenTracing 仅仅定义了两种关系: ChildOf
和 FollowsForm
。如同字面上可以猜测到的, ChildOf
将成为当前 Span 的 child 而 FollowsFrom
则会成为 parent。 这两种关系为 child span 和 parent span 建立了直接因果关系。
ChildOf 引用
: 某个 Span 可以是 ChildOf
的 parent Span
。在一个 ChildOf
的引用中, parent Span
, 在某种程度上取决于child Span
。 下面列举能形成 ChildOf
关系的条件:
- server 端的 RPC 的 Span 可能是
ChildOf
client 端 RPC。 - SQL insert 的 Span 可能是
ChildOf
一个 ORM save 方法的 Span - 许多做并发处理工作的 Span 可能都独立的是
ChildOf
一个单独的 合并这些在截止时间返回工作结果的 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 的结果。如果是这种情况, 那么我们基于因果关系上说Child Span FollowsFrom
parent Span。这里,有很多唯一的 FollowsFrom 引用的子类别。这些会在以后被更加正式的定义。
这些是有效的,基于时间顺序的 FollowFrom
引用:
[-Parent Span-] [-Child Span-]
[-Parent Span--]
[-Child Span-]
[-Parent Span-]
[-Child Span-]
OpenTracing API
在 OpenTracing 有着三个关键的并且相互关联的类型: Tracer
, Span
, SpanContext
。下面,我们来介绍下每种类型的基本行为。 简单地说,每种行为都会在具体的语言中变为一个“方法”,though it may actually be a set of related sibling methods due to type overloading and so on.
在不同语言中,对于 “Optional” 参数的理解是不一样的。 举例来说, Go 里 我们会使用 “functional Options”,但是 Java 里可能会使用 builder 模式。
Tracer
Tracer
interface 创造 Span
s 并且理解 如何 Inject
(serialize) and Extract
(deserialize) them across process boundaries. Formally, it has the following capabilities:
Start a new Span
要求的参数
- 一个 大家都能够理解的 字符串operation name, 并且精确代表了被 Span 做完的工作。 (例如, 一个 RPC 方法名, 一个函数名, 或者 超大计算任务中某个子任务的名字). The operation name 应该 可以辨认一个
Span
实例的最为一般的 string. 这是说,"get_user"
是比"get_user/314159"
更好的.
举例来说,假设我们需要获得账户(account)信息, 下面是一些对于一个 Span
可能的 operation names:
Operation Name | 指导 |
---|---|
get |
过于一般 |
get_account/792 |
过于具体 |
get_account |
赞, 并且 account_id=792 会是一个很好的 Span tag
|
可选择的参数:
- 零个或者多个 references 到相关的
SpanContext
s, 如果可能的话,包括一个对于ChildOf
和FollowsFrom
reference types 简略 - 一个可选的,显性的 start timestamp; 如果被忽略,那么当前 walltime 会被默认使用
- 零个或者多个 tags
返回 一个 已经开始的 Span
实例 (但是尚未结束
)
Inject a SpanContext
into a carrier
要求参数
- 一个
SpanContext
实例 - 一个能告诉
Tracer
如何将SpanContext
编码的 格式 描述 (特别的,对于 string 来说,这个不是必须的) - 一个被 format 所指定的 carrier。某个
Tracer
实现根据这个 format 将SpanContext
编码进入这个 carrier 对象
Extract a SpanContext
from a carrier
要求参数
- 一个 format 描述 (一般但不必要是一个 string 常数)。 目的在于告诉某个
Tracer
实现 如何将SpanContext
carrier 中解码。 - 一个 carrier。它的类型是由format 支配的。某个
Tracer
实现会依据这个 format 将SpanContext
从这个 carrier 对象解码。
**返回一个 SpanContext
实例。 当我们想要通过 Tracer
开始一个新的 Span
, 这个实例是可以被用来当做一个。
Note: required formats for injection and extraction
injection 和 extraction 都依赖于一个可扩展的 **format 参数。 这个参数规定了关联 “carrier” 的类型,同时也描述了一个 SpanContext
是如何被编码进入这个 carrier 的。 所有下面的 formats 都必须被所有的 Tracer 实现支持:
- Text Map: 一个任意 string-to-string 的 map。 Keys 和 values 都是不受任何限制的字符 。
- HTTP Headers: 一个 string-to-string 的 map。Keys 和 values 需要适配 HTTP headers (a la RFC 7230. 实践上来说,因为 HTTP headers 存在被野蛮使用的现象,这里强烈推荐 Tracer 实现限定 HTTP headers 密钥空间(Key Space) 的使用并且保守的将相应的值转义。
-
Binary: 一个 (单独) 任意的 表示 一个
SpanContext
binary blob。
Span
除去 将 Span
's SpanContext
取回的函数,下面任何一个函数都被不会在 Span
结束后被调用。
Retrieve the Span
s SpanContext
没有任何参数。
返回 特定 SpanContext
,对于给定的 Span
. 返回值可能在 Span
结束后仍被使用。
Overwrite the operation name
要求参数。
- 新的 operation name。它将取代在这
Span
开始时传入的 operation name。
Finish the Span
可选参数
-
Span
的 finish timestamp;如果没设定,那么当前的 walltime 就会被使用。
除去 将 Span
's SpanContext
取回的函数,下面任何一个函数都被不会在 Span
结束后被调用。
Set a Span
tag
要求参数
- 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
要求参数
- 一个或者多个 key:value 对。 这些 keys 必须是 string; values 可以有任意类型 一些 OpenTracing 实现可能会处理更多的 log values。
可选参数
- 一个显性的 timestamp. 如何设定,这个值一定是在本地开始时间和 span 结束时间之间。
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, 以及有着直接或者间接 引用关系(reference) 的所有的 Spans。也就是说,baggage items 在随着 trace 一起在频内传播(propagate in-band along with trace itself)。
对于一个 full-stack OpenTracing 集成来说,Baggage items 可实现强大的功能(例如,来自移动应用程序的任意应用程序数据可以透明地、深度地进入存储系统中)中,并带来有一些巨大的成本:请小心驾驶。
谨慎小心地使用此功能。每个键和值都被复制到相关Span的每个本地和远程子元素中,并且这会增加很多网络和CPU开销。
要求参数
- baggage key, 类型为 string
- baggage value, 类型为 string
Get a baggage item
要求参数
- The baggage key, 类型为 string
Returns the corresponding baggage value, xor some indication that such a value was missing.
SpanContext
SpanContext
更像是一个“概念”,而不是通用 OpenTracing 层的有用功能。也就是说,这对于 OpenTracing 实现非常重要,并且确实呈现了一个自己的瘦API。大多数 OpenTracing 用户只能在启动新的 Span
s 时通过引用*与 SpanContext
进行交互。或者在某些传输协议中注入/提取跟踪。
在 OpenTracing 中,我们强制 SpanContext
实例是不可变,以避免 Span
完成和引用周围的复杂生命周期问题。
Iterate through all baggage items
这取决于语言以不同的方式进行建模,但在语义上,调用者应该能够在给定'SpanContext`实例的情况下一次遍历所有的 baggage items。
NoopTracer
所有实现 OpenTracing 语言 API 还必须提供某种 “NoopTracer” 实现,可用于标记控制 OpenTracing 或为测试注入无害的东西(等等)。在某些情况下(例如Java),“NoopTracer” 可能在它自己的打包 artifact 中
Optional API Elements
某些语言还提供实用程序来在单个进程中传入一个活动的“ Span” 和/或 “SpanContext”。例如,opentracing-go
提供基于 Go 的 'context.Context机制 的 helpers 来设置和获取 中的活动
Span`。