OpenTracing 语义说明

最近在为 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 仅仅定义了两种关系: ChildOfFollowsForm。如同字面上可以猜测到的, ChildOf 将成为当前 Span 的 child 而 FollowsFrom则会成为 parent。 这两种关系为 child spanparent span 建立了直接因果关系。

ChildOf 引用: 某个 Span 可以是 ChildOfparent 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 创造 Spans 并且理解 如何 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 到相关的SpanContexts, 如果可能的话,包括一个对于 ChildOfFollowsFrom reference types 简略
  • 一个可选的,显性的 start timestamp; 如果被忽略,那么当前 walltime 会被默认使用
  • 零个或者多个 tags

返回 一个 已经开始的 Span 实例 (但是尚未结束)

Inject a SpanContext into a carrier

要求参数

  • 一个 SpanContext 实例
  • 一个能告诉 Tracer 如何将 SpanContext 编码的 格式 描述 (特别的,对于 string 来说,这个不是必须的)
  • 一个被 format 所指定的 carrier。某个Tracer实现根据这个 formatSpanContext编码进入这个 carrier 对象
Extract a SpanContext from a carrier

要求参数

  • 一个 format 描述 (一般但不必要是一个 string 常数)。 目的在于告诉某个 Tracer 实现 如何将SpanContext carrier 中解码。
  • 一个 carrier。它的类型是由format 支配的。某个 Tracer 实现会依据这个 formatSpanContext 从这个 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 Spans SpanContext

没有任何参数。

返回 特定 SpanContext,对于给定的 Span. 返回值可能在 Span结束后仍被使用。

Overwrite the operation name

要求参数。

  • 新的 operation name。它将取代在这 Span 开始时传入的 operation name
Finish the Span

可选参数

  • Spanfinish 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 用户只能在启动新的 Spans 时通过引用*与 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`。

你可能感兴趣的:(OpenTracing 语义说明)