# 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('...', '...')
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://meetbill.gitbook.io/butterfly-project-doc/project-handlers/cloud_native/log_trace/tracing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
