image.png
注:本文转自好友吴晟的两篇译文 ,译文原文如下: https://github.com/opentracing-contrib/opentracing-specification-zh/blob/master/semantic_conventions.md https://github.com/opentracing-contrib/opentracing-specification-zh/blob/master/specification.md
什么是OpenTracing?
OpenTracing(http://opentracing.io/)是分布式跟踪系统,当我们把系统拆成服务化,分布式系统的时候,查询一个问题,很可能需要多个登录多台机器。
OpenTracing通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加(或更换)追踪系统的实现。OpenTracing正在为全球的分布式追踪,提供统一的概念和数据标准。
关于OpenTracing的一个Java版本的实现请参考如下代码:
https://github.com/opentracing/opentracing-java
OpenTracing标准 描述的语言无关的数据模型,以及OpenTracing API的使用方法。在此数据模型中,包含了两个相关的概念 Span Tag 和 (结构化的) Log Field,尽管在标准中,已经明确了这些操作,但没有定义Span的tag和logging操作时,key的使用规范。
这些语义习惯通过这篇文档进行描述。这篇文档包括两个部分:一. 通过表格罗列出所有的tag和logging操作时,标准的key值。二.描述在特定的典型场景中,如何组合使用这些标准的key值,进行建模。
修改此文件,将影响到OpenTracing标准的版本号。增加内容会增加小版本号,不向前兼容的改变(或新增大量内容)会增加大版本号。
Span的tag作用于 整个Span,也就是说,它会覆盖Span的整个事件周期,所以无需指定特别的时间戳。对于由时间点特性的事件,最好使用Span的log操作进行记录。(在本文档的下一章中进行描述)。
Span tag 名称 | 类型 | 描述与实例 |
---|---|---|
component | string | 生成此Span所相关的软件包,框架,类库或模块。如 "grpc", "django", "JDBI". |
db.instance | string | 数据库实例名称。以Java为例,如果 jdbc.url="jdbc:mysql://127.0.0.1:3306/customers",实例名为 "customers". |
db.statement | string | 一个针对给定数据库类型的数据库访问语句。例如, 针对数据库类型 db.type="sql",语句可能是 "SELECT * FROM wuser_table"; 针对数据库类型为 db.type="redis",语句可能是 "SET mykey 'WuValue'". |
db.type | string | 数据库类型。对于任何支持SQL的数据库,取值为 "sql". 否则,使用小写的数据类型名称,如 "cassandra", "hbase", or "redis". |
db.user | string | 访问数据库的用户名。如 "readonly_user" 或 "reporting_user" |
error | bool | 设置为true,说明整个Span失败。译者注:Span内发生异常不等于error=true,这里由被监控的应用系统决定 |
http.method | string | Span相关的HTTP请求方法。例如 "GET", "POST" |
http.status_code | integer | Span相关的HTTP返回码。例如 200, 503, 404 |
http.url | string | 被处理的trace片段锁对应的请求URL。 例如 "https://domain.net/path/to?resource=here" |
message_bus.destination | string | 消息投递或交换的地址。例如,在Kafka中,在生产者或消费者两端,可以使用此tag来存储"topic name"。 |
peer.address | string | 远程地址。 适合在网络调用的客户端使用。存储的内容可能是"ip:port", "hostname",域名,甚至是一个JDBC的连接串,如 "mysql://prod-db:3306" |
peer.hostname | string | 远端主机名。例如 "opentracing.io", "internal.dns.name" |
peer.ipv4 | string | 远端 IPv4 地址,使用 . 分隔。例如 "127.0.0.1" |
peer.ipv6 | string | 远程 IPv6 地址,使用冒号分隔的元祖,每个元素为4位16进制数。例如 "2001:0db8:85a3:0000:0000:8a2e:0370:7334" |
peer.port | integer | 远程端口。如 80 |
peer.service | string | 远程服务名(针对没有被标准化定义的"service")。例如 "elasticsearch", "a_custom_microservice", "memcache" |
sampling.priority | integer | 如果大于0,Tracer实现应该尽可能捕捉这个调用链。如果等于0,则表示不需要捕捉此调用链。如不存在,Tracer使用自己默认的采样机制。 |
span.kind | string | 基于RPC的调用角色,"client" 或 "server". 基于消息的调用角色,"producer" 或 "consumer" |
每个Span的log操作,都具有一个特定的时间戳(这个时间戳必须在Span的开始时间和结束时间之间),并包含一个或多个 field。下面是标准的field。
Span log field 名称 | 类型 | 描述和实例 |
---|---|---|
error.kind | string | 错误类型(仅在event="error"时使用)。如 "Exception", "OSError" |
error.object | object | 如果当前语言支持异常对象(如 Java, Python),则为实际的Throwable/Exception/Error对象实例本身。例如 一个 java.lang.UnsupportedOperationException 实例, 一个python的 exceptions.NameError 实例 |
event | string | Span生命周期中,特定时刻的标识。例如,一个互斥锁的获取与释放,或 在Performance.timing 规范中描述的,浏览器页面加载过程中的各个事件。 还例如,Zipkin中 "cs", "sr", "ss", 或 "cr". 或者其他更抽象的 "initialized" 或 "timed out"。出现错误时,设置为 "error" |
message | string | 简洁的,具有高可读性的一行事件描述。如 "Could not connect to backend", "Cache invalidation succeeded" |
stack | string | 针对特定平台的栈信息描述,不强制要求与错误相关。如 "File \"example.py\", line 7, in \<module\>\ncaller()\nFile \"example.py\", line 5, in caller\ncallee()\nFile \"example.py\", line 2, in callee\nraise Exception(\"Yikes\")\n" |
使用下面tag为RPC调用建模:
span.kind
: "client"
或 "server"
。在Span开始时,设置此tag是十分重要的,它可能影响内部ID的生成。error
: RPC调用是否发生错误peer.address
, peer.hostname
, peer.ipv4
, peer.ipv6
, peer.port
, peer.service
: 可选tag。描述RPC的对端信息。(一般只有在无法获取到这些信息时,才不设置这些值)消息服务是一个异步调用,所以消费端的Span和生产端的Span使用 Follows From 关系。(查看 Span间关系)
使用下面tag为消息服务建模:
message_bus.destination
: 上表已描述span.kind
: "producer"
或 "consumer"
. 建议 在span开始时 设置此tag,它可能影响内部ID的生成。peer.address
, peer.hostname
, peer.ipv4
, peer.ipv6
, peer.port
, peer.service
: 可选tag,描述消息服务中broker的地址。(可能在内部无法获取)使用下面tag为数据库客户端调用建模:
db.type
, db.instance
, db.user
, 和 db.statement
: 上表已描述peer.address
, peer.hostname
, peer.ipv4
, peer.ipv6
, peer.port
, peer.service
: 描述数据库信息的可选tagspan.kind
: "client"
OpenTracing中,根据语言的不同,错误可以通过不同的方式来进行描述,有一些field是专门针对错误输出的,其他则不是(例如:event
或 message
)
如果存在错误对象,它其中包含栈信息和错误信息,log时使用如下的field:
"error"
<error object instance>
对于其他语言(译者注:不存在上述的错误对象),或上述操作不可行时:
"error"
"..."
"..."
(可选)"..."
(可选)通过此方案,Tracer实现可以在需要时,获取所需的错误信息。
版本号: 1.1
这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念。
OpenTracing标准使用Major.Minor
版本命名策略(即:大版本.小版本),但不包含.Patch
版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型。(如果你想知道更多关于制定此版本政策的原因,可参考specification#2)
OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References。
译者注: Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span.在此译本中,为了便于理解,Span和其他标准内声明的词汇,全部不做名词翻译。
例如:下面的示例Trace就是由8个Span组成:
单个Trace中,span间的因果关系
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]
↑
↑
↑
(Span G 在 Span F 后被调用, FollowsFrom)
有些时候,使用下面这种,基于时间轴的时序图可以更好的展现Trace(调用链):
单个Trace中,span间的时间关系
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
每个Span包含以下的状态:(译者注:由于这些状态会反映在OpenTracing API中,所以会保留部分英文说明)
每一个SpanContext包含以下状态:
一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf
(父子) 和 FollowsFrom
(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。
ChildOf
引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:
下面都是合理的表述一个"ChildOf"关系的父子节点关系的时序图。
[-Parent Span---------]
[-Child Span----]
[-Parent Span--------------]
[-Child Span A----]
[-Child Span B----]
[-Child Span C----]
[-Child Span D---------------]
[-Child Span E----]
FollowsFrom
引用: 一些父级节点不以任何方式依赖他们子节点的执行结果,这种情况下,我们说这些子span和父span之间是"FollowsFrom"的因果关系。"FollowsFrom"关系可以被分为很多不同的子类型,未来版本的OpenTracing中将正式的区分这些类型
下面都是合理的表述一个"FollowFrom"关系的父子节点关系的时序图。
[-Parent Span-] [-Child Span-]
[-Parent Span--]
[-Child Span-]
[-Parent Span-]
[-Child Span-]
OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer
, Span
和 SpanContext
。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法。
当我们讨论“可选”参数时,需要强调的是,不同的语言针对可选参数有不同理解,概念和实现方式 。例如,在Go中,我们习惯使用"functional Options",而在Java中,我们可能使用builder模式。
Tracer
Tracer
接口用来创建Span
,以及处理如何处理Inject
(serialize) 和 Extract
(deserialize),用于跨进程边界传递。它具有如下官方能力:
Span
必填参数
"get_user"
作为操作名,比 "get_user/314159"
更好。例如,假设一个获取账户信息的span会有如下可能的名称:
操作名 | 指导意见 |
---|---|
get | 太抽象 |
get_account/792 | 太明确 |
get_account | 正确的操作名,关于account_id=792的信息应该使用Tag操作 |
可选参数
SpanContext
,如果可能,同时快速指定关系类型,ChildOf
还是 FollowsFrom
。返回值,返回一个已经启动Span
实例(已启动,但未结束。译者注:英语上started和finished理解容易混淆)
SpanContext
上下文Inject(注入)到carrier必填参数
SpanContext
实例Tracer
实现,如何对SpanContext
进行编码放入到carrier中。Tracer
实现根据format声明的格式,将SpanContext
序列化到carrier对象中。SpanContext
上下文从carrier中Extract(提取)必填参数
Tracer
实现,如何从carrier中解码SpanContext
。Tracer
实现根据format声明的格式,从carrier中解码SpanContext
。返回值,返回一个SpanContext
实例,可以使用这个SpanContext
实例,通过Tracer
创建新的Span
。
Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext
是如何编码的。所有的Tracer实现,都必须支持下面的format。
SpanContext
的信息。Span
当Span
结束后(span.finish()
),除了通过Span
获取SpanContext
外,下列其他所有方法都不允许被调用。
Span
获取SpanContext
不需要任何参数。
返回值,Span
构建时传入的SpanContext
。这个返回值在Span
结束后(span.finish()
),依然可以使用。
必填参数
Span
时,传入的操作名。Span
可选参数
Span
设置tag必填参数
注意,OpenTracing标准包含"standard tags,标准Tag",此文档中定义了Tag的标准含义。
必填参数
可选参数
注意,OpenTracing标准包含"standard log keys,标准log的键",此文档中定义了这些键的标准含义。
Baggage元素是一个键值对集合,将这些值设置给给定的Span
,Span
的SpanContext
,以及所有和此Span
有直接或者间接关系的本地Span
。 也就是说,baggage元素随trace一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递)
Baggage元素为OpenTracing的实现全栈集成,提供了强大的功能 (例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统。由于它如此强大的功能,他也会产生巨大的开销,请小心使用此特性。
再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。
必填参数
必填参数
返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如Null)。
SpanContext
相对于OpenTracing中其他的功能,SpanContext
更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。
OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContext
和references,
OpenTracing要求,SpanContext
是不可变的,目的是防止由于Span
的结束和相互关系,造成的复杂生命周期问题。
遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext
实例,高效的遍历所有的baggage元素
NoopTracer
所有的OpenTracing API实现,必须提供某种方式的NoopTracer
实现。NoopTracer
可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer
在他自己的模块中。
有些语言的OpenTracing实现,为了在串行处理中,传递活跃的Span
或SpanContext
,提供了一些工具类。例如,opentracing-go
中,通过context.Context
机制,可以设置和获取活跃的Span
。