Distributed Tracing

Table of Contents

1. 分布式链路追踪

“链路追踪”源于 2010 年 Google 发表的论文 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure

1.1. 为什么需要链路追踪

采用微服务架构开发时,微服务之间的调用链可能会很复杂,通过追踪调用链,我们能直观地观察出各微服务间的调用关系,快速定位出问题的具体服务,同时调用链还可以帮助我们:进行调用耗时分析、可视化错误、进行链路优化等等。

1.2. 核心概念:Span

在微服务环境中,如果想知道一个接口在哪个环节出现了问题,就必须清楚该接口调用了哪些服务,以及调用的顺序,如果把这些服务串起来,看起来就像链条一样,我们称其为调用链。想要实现调用链,就要为每次调用做个标识,然后将服务按标识大小排列,可以更清晰地看出调用顺序,我们将这个调用标识就记作 Span,每个 Span 都有一个唯一的 ID,记为 Spanid,如图 1 所示。

Span 是一个追踪的记录单元,它可以是一个 http 请求,也可以是一个本地函数调用,也可以是一个 rpc 调用或者数据库访问。

tracing_span.jpg

Figure 1: Spanid

实际场景中,我们需要知道某次请求调用的情况,所以只有 spanid 还不够,得为每次请求做个唯一标识,这样才能根据标识查出本次请求调用的所有服务,而这个标识我们命名为 traceid,如图 2 所示。

tracing_traceid.jpg

Figure 2: Traceid

现在根据 spanid 可以轻易地知道被调用服务的先后顺序,但无法体现调用的层级关系,正如图 3 所示,多个服务可能是逐级调用的链条,也可能是同时被同一个服务调用。

tracing_no_parentid.jpg

Figure 3: No parentid

所以应该每次都记录下是谁调用的,我们用 parentid 作为这个标识的名字。

tracing_parentid.jpg

Figure 4: Parentid

到现在,已经知道调用顺序和层级关系了,但是接口出现问题后,还是不能找到出问题的环节,如果某个服务有问题,那个被调用执行的服务一定耗时很长,要想计算出耗时,上述的三个标识还不够,还需要加上时间戳,如图 5 所示。

tracing_timestamp.jpg

Figure 5: Timestamp

综上所述,Span 可以由下面 Golang 代码表示:

type Span struct {
    TraceID    int64        // 用于标识一次完整的请求id
    Name       string
    ID         int64        // 当前这次调用span_id
    ParentID   int64        // 上层服务的调用span_id,最上层服务parent_id为null
    Annotation []Annotation // 用于标记的时间戳
}

注:本节图片和文字整理自博文 技术分析:搞懂链路追踪

2. 侵入式 VS. 非侵入式

我们可以在应用程序代码中增加分布式追踪的相关代码,这称为埋点。这种方式是侵入式的。

除了侵入式的方式外,还有非侵入式的方式来实现链路追踪,如:

  1. 字节码注入(如 pinpoint、skywalking),适用于 Java 等动态语言。
  2. Service Mesh。Service Mesh 一般会被翻译成“服务啮合层”,它是在网络层面做文章,通过 Sidecar 的方式为 Pod 增加一层代理,通过这层网络代理来实现一些服务治理的功能,因为是工作在网络层面,可以做到跨语言、非侵入。Istio 则是目前最成熟的 Service Mash 工具,支持启用分布式追踪服务。Istio 会修改微服务之间发送的网络请求,在请求中注入 Trace 和 Span 标记,再将采集到的数据发送到支持 OpenTracing 的分布式追踪服务中,从而拿到请求在微服务中的调用链。 当然非侵入式也有缺点,它无法追踪某个微服务内部的调用过程,并且目前阶段 Istio 只能追踪 HTTP 请求,能够覆盖的范围比较有限。 如果想追踪更详细的数据,还是需要在中间件和代码中埋点(侵入式),不过好在埋点的过程并不复杂,不会成为一个额外的负担。

参考:https://pjw.io/articles/2018/05/08/opentracing-explanations/

3. Tracing 相关标准

Tracing 的标准主要有三个:

  1. OpenTracing,这个标准 2016 年就开始启动了,为了解决 tracing 系统各自为政,搞了个统一的标准,并提供了各种语言的 api。目前的主要参与者为 Uber,LightStep(Dapper 第一作者的公司),Redhat(Jaeger 开源后,直接放弃自家研发的类似产品,转到了 Jaeger)等公司。
  2. OpenCensus。这里 Google 2018 年初提出的一个集 metrics 和 tracing 的新标准,估计一方面是看到 OpenTracing 想竞争一把,就像之前的容器编排一样,另外也可以推广自己家的 Stackdriver,目前微软也加入了对该标准的支持,准备搞到自己的 Auze 里面。
  3. w3c/distributed-tracing,这里 2017 年提出的一个为了统一各种 tracer 系统中 Context 格式标准,目前还是 Draft 阶段,主要参与者为 Google 和微软。

其中,OpenTracing 的实现最为广泛,如:Jaeger、Appdash、LightStep、Instana、SkyWalking、inspectIT、stagemonitor、Datadog 等等。

注: OpenTracing 和 OpenCensus 目前已经合并成为了一个新标准:OpenTelemetry (informally called OTEL or OTel)。

参考:http://billowqiu.github.io/2018/10/21/distrituted-tracing-history/

4. Jaeger

Jaeger 是 Uber 推出的一款开源分布式追踪系统,兼容 OpenTelemetry API,其架构如图 6 所示,其论文参见:Evolving Distributed Tracing at Uber Engineering

tracing_jaeger.png

Figure 6: Jaeger 架构图

Jaeger 中的 jaeger-agent 组件,部署在宿主机或容器中,专门负责向 collector 异步上报 Span 数据。

Jaeger 有个很有用的特性:支持设置采样率。我们可以设置仅对指定比率(如 50%)的请求开启 Trace,这可以减轻 Trace 功能对系统带来的压力。

在 Docker 中部署 Jaeger 测试环境很简单:

docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.12

参考:https://www.jaegertracing.io/docs/1.12/getting-started/

Author: cig01

Created: <2019-01-20 Sun>

Last updated: <2020-03-10 Tue>

Creator: Emacs 27.1 (Org mode 9.4)