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