基本概念

本文介绍在使用可观测链路 OpenTelemetry 版之前需要了解的基本概念,包括分布式追踪系统的作用,什么是调用链,可观测链路 OpenTelemetry 版所依赖的OpenTracing数据模型,以及在可观测链路 OpenTelemetry 版产品里数据是如何上报的。

为什么需要分布式追踪系统?

为了应对各种复杂的业务,开发工程师开始采用敏捷开发、持续集成等开发方式。系统架构也从单机大型软件演化成微服务架构。微服务构建在不同的软件集上,这些软件模块可能是由不同团队开发的,可能使用不同的编程语言来实现,还可能发布在多台服务器上。因此,如果一个服务出现问题,可能导致几十个应用都出现服务异常。

分布式追踪系统可以记录请求范围内的信息,例如一次远程方法调用的执行过程和耗时,是我们排查系统问题和系统性能的重要工具。

什么是调用链(Trace)?

在广义上,一个调用链代表一个事务或者流程在(分布式)系统中的执行过程。在OpenTracing标准中,调用链是多个Span组成的一个有向无环图(Directed Acyclic Graph,简称DAG),每一个Span代表调用链中被命名并计时的连续性执行片段。

下图是一个分布式调用的例子:客户端发起请求,请求首先到达负载均衡器,接着经过认证服务、计费服务,然后请求资源,最后返回结果。

图 1. 分布式调用示例

image

数据被采集存储后,分布式追踪系统一般会选择使用包含时间轴的时序图来呈现这个调用链。

图 2. 包含时间轴的链路图

image

OpenTracing数据模型

整体概念

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)

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

单个Trace中Span间的时间关系


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

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

链路

Tracer接口用于创建SpanstartSpan函数)、解析上下文(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建立这种关系)。

  • 透传数据

    透传数据分为两步:

    1. 从请求中解析出SpanContext。

      // Inject() takes the `sm` SpanContext instance and injects it for
      // propagation within `carrier`. The actual type of `carrier` depends on
      // the value of `format`.
      /** 根据format参数从请求(Carrier)中解析出SpanContext(包括traceId、spanId、baggage)。
      ** 例如: 
      **  carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
      **  clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
      **/
      Extract(format interface{}, carrier interface{}) (SpanContext, error)
    2. 将SpanContext注入到请求中。

      /**
      ** 将SpanContext中的traceId,spanId,Baggage等根据format参数注入到请求中(Carrier),
      ** e.g 
      ** carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
      ** err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, carrier)
      **/
      Inject(sm SpanContext, format interface{}, carrier interface{}) error

数据是如何上报的?

不通过Agent而直接上报数据的原理如下图所示。

图 3. 直接上报数据

image

通过Agent上报数据的原理如下图所示。

图 4. 通过Agent上报数据

image