第二章 APM 系统预览

347 阅读5分钟

OpenTracing 分布式追踪

当下微服务提供了一套强大的体系结构,但也有其自身的问题,主要体现在调试和跨复杂网络的分布式事务方面,因为没有内存调用或堆栈跟踪日志的追踪。在这样的背景下Google Dapper发布了(Dapper: 大规模分布式系统链路追踪基础设施)论文也是各个分布式追踪的提供基础规范标准。

作用

分布式服务追踪是整个分布式系统中跟踪一个用户请求的过程,包括数据采集、数据传输、数据存储、数据分析和数据可视化,捕获此类跟踪让我们构建用户交互背后的整个调用链的视图,这是调试和监控微服务的关键工具。系统开发人员只需将 Trace 组件嵌入到基础通用库中,就可以正常运行,而应用层开发者则不需要关心具体 Trace 组件实现、集成方式,达到以应用层透明的方式嵌入各个模块的目的

三个设计目标

  • 开销低 分布式系统对性能和资源要求都很严格,Trace组件对服务的影响必须足够小。如果在高度敏感的系统中监控的开销比例占比较大,会导致直接服务降级处理

  • 透明 对业务系统透明。Trace组件嵌入到基础通用库中,以提高稳定性,应用开发者不需要关心它们

  • 扩展性 水平拓展能力。也就是经常说的集群能力

非常重要的三个对象

  • Trace 表示一次请求的完整的树形结构来记录请求之间的关系,一个Trace 由一个Span或者多个Spen 组合而来。
  • SpanContext:分布式追踪的上下文信息,包括 Trace id,Span id 以及其它需要传递到下游服务的内容。一个 OpenTracing 的实现需要将 SpanContext 通过某种序列化协议 (Wire Protocol) 在进程边界上进行传递,以将不同进程中的 Span 关联到同一个 Trace 上。对于 HTTP 请求来说,SpanContext 一般是采用 HTTP header 进行传递的
  • Span 翻译跨度,可以被理解为一次方法调用,一个程序块的调用,或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问都可以用 Span 表示

Trace 和 Span 组成了一个调用链:Trace 代表了一个端到端的分布式调用,Span 是该调用中间的一段。SpanContext 则用于将一个 Span 的上下文传递到其下游的 Span 中,以将这些 Span 关联起来

数据模型

一个 Trace 可以看成由多个相互关联的 Span 组成的有向无环图

[Span A]  ←←←(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 也可以按照时间按照时间先后顺序进行排列

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

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

一个 Span 的数据结构中包含以下内容:

  • name: Span 所代表的操作名称,例如 REST 接口对应的资源名称。
  • Start timestamp: Span 所代表操作的开始时间。
  • Finish timestamp: Span 所代表的操作的的结束时间。
  • Logs:一系列摘要日志,每个摘要日志由一个键值对组成,包含Span内的错误堆栈与事件信息。
  • Tags:一系列标签,每个标签由一个键值对组成。该标签可以是任何有利于调用分析的信息,例如方法名,URL 等。
  • SpanContext:用于跨进程边界传递 Span 相关信息,在进行传递时需要结合一种序列化协议 (Wire Protocol) 使用。
  • References:该Span引用的其它关联 Span,主要有两种引用关系,Childof 和 FollowsFrom。
    • Childof: 最常用的一种引用关系,表示 Parent Span 和 Child Span 之间存在直接的依赖关系。例 PRC 服务端 Span 和 RPC 客户端 Span,或者数据库 SQL 插入 Span 和 ORM Save 动作 Span 之间的关系。
    • FollowsFrom:如果 Parent Span 并不依赖 Child Span 的执行结果,则可以用 FollowsFrom 表示。例如网上商店购物付款后会向用户发一个邮件通知,但无论邮件通知是否发送成功,都不影响付款成功的状态,这种情况则适用于用 FollowsFrom 表示。

跨进程调用信息传播

SpanContext 是 OpenTracing 中一个让人比较迷惑的概念。在 OpenTracing 的概念模型中我们讲到 SpanContext 用于跨进程边界传递分布式调用的上下文,但实际上 OpenTracing 只定义一个 SpanContext 的抽象接口,该接口封装了分布式调用中一个 Span 的相关上下文内容,包括该 Span 所属的 Trace id,Span id 以及其它需要传递到下游服务的信息。SpanContext 自身并不能实现跨进程的上下文传递,而是需要由 Tracer(Tracer 是一个遵循 OpenTracing 协议的实现,如 Jaeger,Skywalking 的 Tracer) 将 SpanContext 序列化后通过 Wire Protocol 传递到下一个进程中,然后在下一个进程将 SpanContext 反序列化,得到相关的上下文信息,以用于生成 Child Span。

为了为各种具体实现提供最大的灵活性,OpenTracing 只是提出了跨进程传递 SpanContext 的要求,并未规定将 SpanContext 进行序列化并在网络中传递的具体实现方式。各个不同的 Tracer 可以根据自己的情况使用不同的 Wire Protocol 来传递 SpanContext。

在基于 HTTP 协议的分布式调用中,通常会使用 HTTP Header 来传递 SpanContext 的内容。常见的 Wire Protocol 包含 Zipkin 使用的 b3 HTTP header,Jaeger 使用的 uber-trace-id HTTP Header,LightStep 使用的 "x-ot-span-context" HTTP Header 等。

参考文档地址