OpenTracing语义标准规范及实现

OpenTracing语义标准规范及实现_第1张图片
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 和 log field

Span tag 清单

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"

Log field 清单

每个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 \\ncaller()\nFile \"example.py\", line 5, in caller\ncallee()\nFile \"example.py\", line 2, in callee\nraise Exception(\"Yikes\")\n"

典型场景建模

RPCs

使用下面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的对端信息。(一般只有在无法获取到这些信息时,才不设置这些值)

Message Bus

消息服务是一个异步调用,所以消费端的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的地址。(可能在内部无法获取)

Database (client) calls

使用下面tag为数据库客户端调用建模:

  • db.type, db.instance, db.user, 和 db.statement: 上表已描述
  • peer.address, peer.hostname, peer.ipv4, peer.ipv6, peer.port, peer.service: 描述数据库信息的可选tag
  • span.kind: "client"

Captured errors,捕获错误

OpenTracing中,根据语言的不同,错误可以通过不同的方式来进行描述,有一些field是专门针对错误输出的,其他则不是(例如:eventmessage

如果存在错误对象,它其中包含栈信息和错误信息,log时使用如下的field:

  • event="error"
  • error.object=

对于其他语言(译者注:不存在上述的错误对象),或上述操作不可行时:

  • event="error"
  • message="..."
  • stack="..." (可选)
  • error.kind="..." (可选)

通过此方案,Tracer实现可以在需要时,获取所需的错误信息。

三、OpenTracing语义标准

版本号: 1.1

综述

这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念。

版本命名策略

OpenTracing标准使用Major.Minor版本命名策略(即:大版本.小版本),但不包含.Patch版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型。(如果你想知道更多关于制定此版本政策的原因,可参考specification#2)

OpenTracing数据模型

OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。
特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图),
SpanSpan的关系被命名为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中,所以会保留部分英文说明)

  • An operation name,操作名称
  • A start timestamp,起始时间
  • A finish timestamp,结束时间
  • Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型。
  • Span Log,一组span的日志集合。
    每次log操作包含一个键值对,以及一个时间戳。
    键值对中,键必须为string,值可以是任意类型。
    但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型。
  • SpanContext,Span上下文对象 (下面会详细说明)
  • References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)

每一个SpanContext包含以下状态:

  • 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输
  • Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输

Span间关系

一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)。这两种关系明确的给出了两个父子关系的Span的因果模型。 将来,OpenTracing可能提供非因果关系的span间关系。(例如:span被批量处理,span被阻塞在同一个队列中,等等)。

ChildOf 引用: 一个span可能是一个父级span的孩子,即"ChildOf"关系。在"ChildOf"引用关系下,父级span某种程度上取决于子span。下面这些情况会构成"ChildOf"关系:

  • 一个RPC调用的服务端的span,和RPC服务客户端的span构成ChildOf关系
  • 一个sql insert操作的span,和ORM的save方法的span构成ChildOf关系
  • 很多span可以并行工作(或者分布式工作)都可能是一个父级的span的子项,他会合并所有子span的执行结果,并在指定期限内返回

下面都是合理的表述一个"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 API

OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer, SpanSpanContext。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法。

当我们讨论“可选”参数时,需要强调的是,不同的语言针对可选参数有不同理解,概念和实现方式 。例如,在Go中,我们习惯使用"functional Options",而在Java中,我们可能使用builder模式。

Tracer

Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递。它具有如下官方能力:

创建一个新Span

必填参数

  • operation name, 操作名, 一个具有可读性的字符串,代表这个span所做的工作(例如:RPC方法名,方法名,或者一个大型计算中的某个阶段或子任务)。操作名应该是一个抽象、通用,明确、具有统计意义的名称。因此,"get_user" 作为操作名,比 "get_user/314159"更好。

例如,假设一个获取账户信息的span会有如下可能的名称:

操作名 指导意见
get 太抽象
get_account/792 太明确
get_account 正确的操作名,关于account_id=792的信息应该使用Tag操作

可选参数

  • 零个或者多个关联(references)的SpanContext,如果可能,同时快速指定关系类型,ChildOf 还是 FollowsFrom
  • 一个可选的显性传递的开始时间;如果忽略,当前时间被用作开始时间。
  • 零个或者多个tag

返回值,返回一个已经启动Span实例(已启动,但未结束。译者注:英语上started和finished理解容易混淆)

SpanContext上下文Inject(注入)到carrier

必填参数

  • SpanContext实例
  • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何对SpanContext进行编码放入到carrier中。
  • carrier,根据format确定。Tracer实现根据format声明的格式,将SpanContext序列化到carrier对象中。

SpanContext上下文从carrier中Extract(提取)

必填参数

  • format(格式化)描述,一般会是一个字符串常量,但不做强制要求。通过此描述,通知Tracer实现,如何从carrier中解码SpanContext
  • carrier,根据format确定。Tracer实现根据format声明的格式,从carrier中解码SpanContext

返回值,返回一个SpanContext实例,可以使用这个SpanContext实例,通过Tracer创建新的Span

注意,对于Inject(注入)和Extract(提取),format是必须的。

Inject(注入)和Extract(提取)依赖于可扩展的format参数。format参数规定了另一个参数"carrier"的类型,同时约束了"carrier"中SpanContext是如何编码的。所有的Tracer实现,都必须支持下面的format

  • Text Map: 基于字符串:字符串的map,对于key和value不约束字符集。
  • HTTP Headers: 适合作为HTTP头信息的,基于字符串:字符串的map。(RFC 7230.在工程实践中,如何处理HTTP头具有多样性,强烈建议tracer的使用者谨慎使用HTTP头的键值空间和转义符)
  • Binary: 一个简单的二进制大对象,记录SpanContext的信息。

Span

Span结束后(span.finish()),除了通过Span获取SpanContext外,下列其他所有方法都不允许被调用。

除了通过Span获取SpanContext

不需要任何参数。

返回值Span构建时传入的SpanContext。这个返回值在Span结束后(span.finish()),依然可以使用。

复写操作名(operation name)

必填参数

  • 新的操作名operation name,覆盖构建Span时,传入的操作名。

结束Span

可选参数

  • 一个明确的完成时间;如果省略此参数,使用当前时间作为完成时间。

Span设置tag

必填参数

  • tag key,必须是string类型
  • tag value,类型为字符串,布尔或者数字

注意,OpenTracing标准包含"standard tags,标准Tag",此文档中定义了Tag的标准含义。

Log结构化数据

必填参数

  • 一个或者多个键值对,其中键必须是字符串类型,值可以是任意类型。某些OpenTracing实现,可能支持更多的log值类型。

可选参数

  • 一个明确的时间戳。如果指定时间戳,那么它必须在span的开始和结束时间之内。

注意,OpenTracing标准包含"standard log keys,标准log的键",此文档中定义了这些键的标准含义。

设置一个baggage(随行数据)元素

Baggage元素是一个键值对集合,将这些值设置给给定的SpanSpanSpanContext,以及所有和此Span有直接或者间接关系的本地Span 也就是说,baggage元素随trace一起保持在带内传递。(译者注:带内传递,在这里指,随应用程序调用过程一起传递)

Baggage元素为OpenTracing的实现全栈集成,提供了强大的功能 (例如:任意的应用程序数据,可以在移动端创建它,显然的,它会一直传递了系统最底层的存储系统。由于它如此强大的功能,他也会产生巨大的开销,请小心使用此特性。

再次强调,请谨慎使用此特性。每一个键值都会被拷贝到每一个本地和远程的下级相关的span中,因此,总体上,他会有明显的网络和CPU开销。

必填参数

  • baggage key, 字符串类型
  • baggage value, 字符串类型

获取一个baggage元素

必填参数

  • baggage key, 字符串类型

返回值,相应的baggage value,或者可以标识元素值不存在的返回值(译者注:如Null)。

SpanContext

相对于OpenTracing中其他的功能,SpanContext更多的是一个“概念”。也就是说,OpenTracing实现中,需要重点考虑,并提供一套自己的API。
OpenTracing的使用者仅仅需要,在创建span、向传输协议Inject(注入)和从传输协议中Extract(提取)时,使用SpanContextreferences

OpenTracing要求,SpanContext不可变的,目的是防止由于Span的结束和相互关系,造成的复杂生命周期问题。

遍历所有的baggage元素

遍历模型依赖于语言,实现方式可能不一致。在语义上,要求调用者可以通过给定的SpanContext实例,高效的遍历所有的baggage元素

NoopTracer

所有的OpenTracing API实现,必须提供某种方式的NoopTracer实现。NoopTracer可以被用作控制或者测试时,进行无害的inject注入(等等)。例如,在 OpenTracing-Java实现中,NoopTracer在他自己的模块中。

可选 API 元素

有些语言的OpenTracing实现,为了在串行处理中,传递活跃的SpanSpanContext,提供了一些工具类。例如,opentracing-go中,通过context.Context机制,可以设置和获取活跃的Span

你可能感兴趣的:(OpenTracing语义标准规范及实现)