版本: 1.1
文件概述
这是“正式的”OpenTracing 语义规范。由于 OpenTracing 必须跨语言工作,因此本文档注意避免使用特定语言的概念。也就是说,所有语言都有同一种理解,即都有一些“接口”的概念,封装了一组相关的功能。
版本策略
OpenTracing采用了 主版本号.次版本号 形式的版本号,但是没有 .修订号 。当对规范进行向后不兼容的更改时,主版本增加。次版本的增加则用于不间断更改,如引入新的标准标记,日志字段或SpanContext引用类型。
蓝图: OpenTracing的适用范围
OpenTracing的核心协议(即本文档)有意跟踪特定下游或监视系统中不可知的细节。这是因为在OpenTracing中,存在用于在分布式系统中描述交换的语义。描述这些交换时,不应受到特定后端人员处理或表示数据方式的影响。例如,可以使用详细的Opentracing设施来收集和聚合指标作为时间序列数据(例如Prometheus);或span启动+结束时间的日志可以聚合到日志中心服务(例如Kibana)。
因此,OpenTracing 规范和数据建模语义约定比某些追踪系统具有更广泛的范围,“这没关系”。如果某些语义行为超出了特定追踪或监控系统的范围,则所述系统可以汇总或简单地忽略从 OpenTracing 工具中流出的相应数据。
OpenTracing的数据模型
OpenTracing中的Traces由其Span隐式定义。Trace可被认为是由一组Span定义的有向无环图(DAG),在Span之间的被称为References。
以下是一个由8个Span构成的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)
有时用时间轴来显示Traces更容易,如下图所示:
单个Trace中Span间的时间关系
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每个Span都封装了以下状态
- 操作名称
- 开始时间戳
- 结束时间戳
- 一组零或多个键:值结构的Span标签(Tags)。键必须是字符串。值可以是字符串,布尔或数值类型.
- 一组零或多个Span日志(Logs),其中每个都是一个键:值映射并与一个时间戳配对。键必须是字符串,值可以是任何类型。 并非所有的OpenTracing实现都必须支持每种值类型。
- 一个SpanContext(见下文)
- 零或多个因果相关的Span间的References (通过那些相关的Span的SpanContext)
每个SpanContext都封装了以下状态:
- 任何需要跟跨进程Span关联的,依赖于OpenTracing实现的状态(例如Trace和Span的id)
- 键:值结构的跨进程的Baggage Items
Span间的关联
一个Span可能会引用零个或多个因果相关的SpanContexts,opentracing目前定义了两种References:ChildOf 与 FollowsFrom。两种类型的References都对父子span之间的因果关系进行的建模。未来,OpenTracing可能为不具有因果关系的span提供References类型。(例如批量的Span,卡在队列中的Span等)
ChildOfreference:一个Span可以是另一个Span的子Span。在ChildOf引用中,父Span在某种程度上取决于子Span。下列情况会构成ChildOf关系:- 在一个RPC中,代表服务端的Span可作为
ChildOf代表客户端的Span - 在一个持久化进程中,代表SQL插入的Span可作为
ChildOf,来代表ORM save方法的Span - 执行并发(或者分布式)任务的span也许都作为
ChildOf归属于一个父span,父span会合并所有子项的数据,并在截止时间内返回
- 在一个RPC中,代表服务端的Span可作为
下列这些都是有效的具有 ChildOf 关系的时序图
[-Parent Span---------]
[-Child Span----]
[-Parent Span--------------]
[-Child Span A----]
[-Child Span B----]
[-Child Span C----]
[-Child Span D---------------]
[-Child Span E----]
FollowsFromreference: 有些父Span不依赖于任何子Span的结果。这种情况下,我们仅认为子Span在因果上FollowsFrom父Span。有许多不同的FollowsFrom引用子类别,在OpenTracing的未来版本中,它们可能会被更正式地区分。
下列这些都是有效的具有 FollowsFrom 关系的时序图
[-Parent Span-] [-Child Span-]
[-Parent Span--]
[-Child Span-]
[-Parent Span-]
[-Child Span-]
OpenTracing接口定义
Opentracing规范中有三种相互关联的关键类型:Tracer,SPAN和SPANCONTEXT。下面,我们仔细研究每种类型的行为;粗略地说,每种行为在典型的编程语言中都变成“Method”,尽管由于类型重载等原因,它实际上可能是一组相关的同级方法。
当我们讨论可选参数时,应当明白不同开发语言拥有不同的方式去解释这个概念。例如,在Go语言中,我们会使用”functional Options”这个术语,而在Java中我们会使用builder模式。
Tracer
Tracer接口用于创建span以及描述如何去跨进程地Inject (序列化) 以及 Extract (反序列化)。严格来说,它应具有以下能力:
开始一个新的Span
必须参数
- 操作名称,一个人工可读的字符串,它简洁地表示由Span完成的工作 (例如,RPC方法名称、函数名称或一个较大的计算任务中的阶段的名称)。操作名称应该用泛化的字符串形式标识出一个Span实例. 也就是说,
get_user比get_user/314159好。
比如说这里有几个操作名称,用于得到一个虚构的账户信息:
| 操作名称 | 建议 |
|---|---|
get | 太宽泛 |
get_account/792 | 太具体 |
get_account | 刚刚好,account_id=792 可作为一个合适的 Span 标签 |
可选参数
- 零个或多个与
SpanContext相关的引用,尽可能包括ChildOf和FollowFrom引用类型的信息 - 开始时间戳,如果没有的话,默认使用现实时间(walltime)
- 零或多个标签
返回一个已启动但未完成的Span实例。
注入(Inject) SpanContext到载体中
必须参数
- 格式描述符**(format descriptor)** ,告诉Tracer的实现如何在载体对象中对SpanContext进行解码
- 载体,其类型由格式描述符指定。 Tracer的实现将根据格式描述对此载体对象中的SpanContext进行解码
返回值
SpanContext实例,适合作为通过Tracer启动Span时的reference
注意: Injection和Extraction所需的格式
Injection和Extraction都依赖于一个可扩展的格式描述符参数,其指定了相关联的”载体”类型以及 SpanContext 是如何被编码的。Tracer实现必须支持下列所有格式。
- 文本映射(Text Map): 任意的无字符编码限制的string-string型键值结构
- HTTP Headers: string-string型的键值结构在HTTP headers中也适用。在实操中,由于HTTP header很容易被各种不同的方式处理,强烈建议在Tracer的实现中使用有限的键空间和保守的转义值
- 二进制: 代表了
SpanContext的任意二进制数据
Span
除了获取 Span 的 SpanContext 的方法之外,下面任何方法都不能在 Span 完成后被调用。
获取 Span 的 SpanContext
- 无需参数
- 返回值是
Span的SpanContext。返回值甚至可能在Span结束后被使用。 - 重写操作名
- 必须参数:新的操作名,当
Span启动时取代旧内容
- 必须参数:新的操作名,当
- 完成Span
- 可选参数:结束时间戳/当前时间
- 设置一个Span标签
- 必须参数
- 标签的键,必须是字符串
- 标签的值,必须是字符串,布尔值,数值类型其中之一
- 注意OpenTracing项目文档中已经为一些”标准标签”规定了语义。
- 必须参数
- 记录结构化数据
- 必须参数
- 一到多个键值对,键必须是字符串,值可以是任意类型。有些OpenTracing的实现可以处理更多的日志的值。
- 可选参数
- 显式时间戳。该参数必须落在当前Span的开始与结束时间戳之间。
- 必须参数
- 设置一个
baggage item- Baggage item是对应于某个
Span及其SpanContext,以及所有直接或间接引用自本地(local)Span的Span的键值对。也就是说,baggage items是与其trace一起传播的。 - Baggage item是对应于某个
Span及其SpanContext,以及所有直接或间接引用自本地(local)Span的Span的键值对。也就是说,baggage items是与其trace一起传播的。 - 每个键值都会被拷贝到每一个本地(local)及远程的子Span,这可能导致巨大的网络和CPU开销。
- 必须参数
- baggage的键值(字符串)
- Baggage item是对应于某个
- 获取一个
baggage item- 必须参数
- baggage的键值(字符串)
- 必须参数
SpanContext
Spancontext更像是“概念”,而不是通用opentracing层的功能。也就是说,这对OpenTracing的实现来说提供一层薄的接口是至关重要的。当启动一个 Span 或者注入/提取(injecting/extracting) trace时,多数OpenTracing用户仅通过reference与 SpanContext 进行交互。
在OpenTracing中,SpanContext 被强制设定为不可变的(immutable) ,以应对在 Span 结束时和引用(reference)时产生的复杂的生命周期问题。
遍历所有的baggage item
不同语言对此有不同的建模方式,不过给出一个 SpanContext 实例,语义上还是应该能让调用者在短时间内遍历baggage items。
NoopTracer
所有语言的OpenTracing API必须提供某种 NoopTracer 实现,可用作标记控制(flag-control)或者进行一些用于测试的无害注入等。某些情况下(比如Java),NoopTracer 可能在它自己的制品包(packaging artifact)中。
可选的API元素
有些语言提供了在单进程中用来传递活动的 Span 和 SpanContext 的工具。比如,opentracing-go 提供了在Go的 context.Context 机制中,用来set和get活动 Span 的帮助函数(helpers)。