Tracing

OpenTracing 数据模型

  • 1 分布式追踪的概念

  • 2 整体概念

    • 2.1 链路

  • 3 业界

  • 4 例子

    • 4.1 opentracing

      • 4.1.1 Scope & ScopeManager

      • 4.1.2 使用

1 分布式追踪的概念

谷歌在 2010 年 4 月发表了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》(http://1t.click/6EB),介绍了分布式追踪的概念。

对于分布式追踪,主要有以下的几个概念:

  • 追踪 Trace:就是由分布的微服务协作所支撑的一个事务。一个追踪,包含为该事务提供服务的各个服务请求。

  • 跨度 Span:Span 是事务中的一个工作流,一个 Span 包含了时间戳,日志和标签信息。Span 之间包含父子关系,或者主从(Followup)关系。

  • 跨度上下文 Span Context:跨度上下文是支撑分布式追踪的关键,它可以在调用的服务之间传递,上下文的内容包括诸如:从一个服务传递到另一个服务的时间,追踪的 ID,Span 的 ID 还有其它需要从上游服务传递到下游服务的信息。

2 整体概念

OpenTracing 中的调用链(Trace)通过归属于此调用链的 Span 来隐性地定义。一条调用链可以视为一个由多个 Span 组成的有向无环图(DAG 图)。Span 之间的关系被命名为 References。例如下面的示例调用链就是由 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)

Span,可以被翻译为跨度,可以被理解为一次方法调用,一个程序块的调用,或者一次 RPC/数据库访问。
只要是一个具有完整时间周期的程序访问,都可以被认为是一个 span.

有些情况下,使用下面这种基于时间轴的时序图可以更好地展现调用链。

单个 Trace 中 Span 间的时间关系

––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

[Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]

2.1 链路

Tracer 接口用于创建 Span(startSpan 函数)、解析上下文(Extract 函数)和透传上下文(Inject 函数)。它具有以下能力:

创建一个新 Span 或者设置 Span 属性

/** 创建和开始一个 span,返回一个 span,span 中包括操作名称和设置的选项。
** 例如:
**    创建一个无 parentSpan 的 Span:
**    sp := tracer.StartSpan("GetFeed")
**   创建一个有 parentSpan 的 Span
**    sp := tracer.StartSpan("GetFeed",opentracing.ChildOf(parentSpan.Context()))
**/
StartSpan(operationName string, opts ...StartSpanOption) Span

每个 Span 包含以下对象:

  • Operation name:操作名称 (也可以称作 Span name)。

  • Start timestamp:起始时间。

  • Finish timestamp:结束时间。

  • Span tag:一组键值对构成的 Span 标签集合。键值对中,键必须为 String,值可以是字符串、布尔或者数字类型。

  • Span log:一组 Span 的日志集合。每次 Log 操作包含一个键值对和一个时间戳。键值对中,键必须为 String,值可以是任意类型。

  • SpanContext: Span 上下文对象。每个 SpanContext 包含以下状态:

要实现任何一个 OpenTracing,都需要依赖一个独特的 Span 去跨进程边界传输当前调用链的状态(例如:Trace 和 Span 的 ID)。

Baggage Items 是 Trace 的随行数据,是一个键值对集合,存在于 Trace 中,也需要跨进程边界传输。

References(Span 间关系):相关的零个或者多个 Span(Span 间通过 SpanContext 建立这种关系)。

3 业界

  • Dapper(Google) : 各 tracer 的基础

  • StackDriver Trace (Google)

  • Zipkin(twitter)

  • Appdash(golang)

  • 鹰眼(taobao)

  • 谛听(盘古,阿里云云产品使用的Trace系统)

  • 云图(蚂蚁Trace系统)

  • sTrace(神马)

  • X-ray(aws)

4 例子

4.1 opentracing

  • SDK: https://github.com/opentracing/opentracing-python

  • eg: https://github.com/uber-common/opentracing-python-instrumentation

  • https://github.com/opentracing-contrib/python-flask

  • https://github.com/opentracing-contrib/python-django

4.1.1 Scope & ScopeManager

  • Scope 对象是 Active Span 的容器;

    • 通过 Scope 能拿到当前上下文内的 Active Span; _ThreadLocalScope 是 Scope 的一个实现,通过 ThreadLocal 来存储

  • ScopeManager 用来管理 Scope, ScopeManager 抽象了激活当前 Span 实例方法(via activate(Span)) 和 访问当前活跃 Span 实例的方法 (via activeSpan() );

    • ThreadLocalScopeManager 是 ScopeManager 的一个实现

Q: 为什么要抽象出 Scope 的概念?直接使用 ThreadLocal 存储 Span 不就可以了吗?

首先理解 Scope 是什么?
Scope 是 Active Span 的一个容器,Scope 代表着当前活跃的 Span; 是对当前活跃 Span 的一个抽象,代表了当前上下文所处于的一个过程;

另外, ThreadLocalScope 还记录了 _to_restore Span, 这样结束时,可以恢复到上一个 Span 的状态;

我理解如果只是 get_current_span() 逻辑的话,直接把 span 塞到 ThreadLocal 里就可以在线程内传递了;
但是 ScopeManager 看代码是这样实现的,ScopeManager 包含一个 Scope, Scope 又包含了当前 Span 和 recover Scope;
好处是:这样就保证了,如果开启一个子 Span(子 span 会产生孙子 span),子 span 结束后,还可以回到 父span (这样可以继续产生以父 span 为基础的兄弟 span)
        如果只是 ThreadLocal 里塞一个当前 span的话,是解决不了这种情况的。

4.1.2 使用

# Manual activation of the Span.
span = tracer.start_span(operation_name='someWork')
with tracer.scope_manager.activate(span, True) as scope:
   # Do things.

# Automatic activation of the Span.
# finish_on_close is a required parameter.
with tracer.start_active_span('someWork', finish_on_close=True) as scope:
   # Do things.

# Handling done through a try construct:
span = tracer.start_span(operation_name='someWork')
scope = tracer.scope_manager.activate(span, True)
try:
   # Do things.
except Exception as e:
   scope.set_tag('error', '...')
finally:
   scope.finish()

Accessing the Active Span

# Access to the active span is straightforward.
scope = tracer.scope_manager.active()
if scope is not None:
    scope.span.set_tag('...', '...')

Last updated