说一说分布式系统的链路追踪(续)

688 阅读12分钟

1.Zipkin

Zipkin是一个分布式跟踪系统。它有助于收集解决服务体系结构中的延迟问题所需的时序数据。功能包括该数据的收集和查找。

如果日志文件中有 trace ID,则可以直接跳至该trace ID。否则,您可以基于服务、操作名称、标记和持续时间等属性进行查询。

为您汇总了一些有趣的数据,比如花费在服务上的时间百分比,以及操作是否失败。

Zipkin UI还提供了一个依赖关系图,显示每个应用程序经过了多少跟踪请求。这有助于识别聚合行为,包括错误路径或对已弃用服务的调用。

需要对应用程序进行检测,以便向Zipkin报告跟踪数据。这通常意味着配置跟踪程序或检测库。 最流行的向Zipkin报告数据的方式是通过HTTP或Kafka,尽管还有许多其他的选择,如Apache ActiveMQ, gRPC和RabbitMQ。

提供给UI的数据存储在内存中,或者通过Apache Cassandra或Elasticsearch等受支持的后端持久存储。

开启一个Zipkin

docker run -d -p 9411:9411 openzipkin/zipkin

架构

Tracer 存在于应用程序中,并记录有关发生的操作的时间和元数据。 他们经常使用仪器库,这样它们的使用对用户是透明的。例如,仪表化的web服务器记录它何时接收到请求,何时发送响应。收集的跟踪数据称为Span。

Instrumentation在生产环境中是安全的,开销很小.由于这个原因,它们只传播id在带内,以告诉接收方进程中有一个trace。已完成的Span将报告给带外的Zipkin,类似于应用程序异步报告指标的方式。

例如,当跟踪一个操作时,需要在发出http请求时,添加一些header来传播id。Header不用于发送操作名称等详细信息。

在一个仪表化的应用程序中,向Zipkin发送数据的组件被称为报告器Reporter.Reporter通过几种传输方式之一发送跟踪数据到Zipkin收集器,收集器将跟踪数据持久化到存储中。 之后,存储通过查询Api将数据提供给UI。

这里有一个图表描述了这个流程

示例

正如概述中提到的,标识符在带内发送,细节信息在带外发送给Zipkin。在这两种情况下,跟踪instrumentation都负责创建有效的跟踪并正确地呈现它们。例如,跟踪程序确保它在带内(下游)和带外(异步到Zipkin)发送的数据之间具有一致性。

下面是一个http跟踪的示例序列,其中用户代码调用了资源/foo

┌─────────────┐ ┌───────────────────────┐  ┌─────────────┐  ┌──────────────────┐
│ User Code   │ │ Trace Instrumentation │  │ Http Client │  │ Zipkin Collector │
└─────────────┘ └───────────────────────┘  └─────────────┘  └──────────────────┘
       │                 │                         │                 │
           ┌─────────┐
       │ ──┤GET /foo ├─▶ │ ────┐                   │                 │
           └─────────┘         │ record tags
       │                 │ ◀───┘                   │                 │
                           ────┐
       │                 │     │ add trace headers │                 │
                           ◀───┘
       │                 │ ────┐                   │                 │
                               │ record timestamp
       │                 │ ◀───┘                   │                 │
                             ┌─────────────────┐
       │                 │ ──┤GET /foo         ├─▶ │                 │
                             │X-B3-TraceId: aa │     ────┐
       │                 │   │X-B3-SpanId: 6b  │   │     │           │
                             └─────────────────┘         │ invoke
       │                 │                         │     │ request   │
                                                         │
       │                 │                         │     │           │
                                 ┌────────┐          ◀───┘
       │                 │ ◀─────┤200 OK  ├─────── │                 │
                           ────┐ └────────┘
       │                 │     │ record duration   │                 │
            ┌────────┐     ◀───┘
       │ ◀──┤200 OK  ├── │                         │                 │
            └────────┘       ┌────────────────────────────────┐
       │                 │ ──┤ asynchronously report span     ├────▶ │
                             │                                │
                             │{                               │
                             │  "traceId": "aa",              │
                             │  "id": "6b",                   │
                             │  "name": "get",                │
                             │  "timestamp": 1483945573944000,│
                             │  "duration": 386000,           │
                             │  "annotations": [              │
                             │--snip--                        │
                             └────────────────────────────────┘

这将导致单个span,在用户代码接收到http响应后异步发送给Zipkin。

Trace instrumentation 异步报告span,为了防止由于跟踪系统的延迟或者故障导致延迟或者破坏用户代码。

传输

由插装库发送的Span必须从跟踪的服务传输到Zipkin收集器。主要有三种传输方式:HTTPKafkaScribe

组件: 组成Zipkin的组件有4个:

  • collector收集器:跟踪数据到达Zipkin collector守护进程后,将对其进行验证、存储和索引,以便由Zipkin收集器进行查找。
  • storage 存储:Zipkin最初是为了在Cassandra上存储数据而构建的,因为Cassandra是可伸缩的,有一个灵活的模式,并且在Twitter中大量使用。但是,我们使这个组件可插拔。除了Cassandra,我们还支持ElasticSearch和MySQL。其他后端可以作为第三方扩展提供。
  • search 搜索:一旦数据被存储并建立了索引,我们需要一种方法来提取它。查询守护进程为查找和检索跟踪提供了一个简单的JSON API。这个API的主要使用者是Web UI。
  • web UI:我们创建了一个GUI,它为查看跟踪提供了一个很好的界面。web UI提供了一种基于服务、时间和注释查看跟踪的方法.注意:UI中没有内置的身份验证.

2.Jaeger

Jaeger受到Dapper和OpenZipkin的启发,是Uber技术公司发布的开源分布式追踪系统。 它用于监视和诊断基于微服务的分布式系统,包括:

  • 分布式上下文传播
  • 分布式事务监控
  • 根本原因分析
  • 服务依赖关系分析
  • 性能/延迟优化

Uber在Uber上发表了一篇博客文章《不断发展的分布式追踪》,其中解释了Jaeger在架构选择方面的历史和原因。 Jaeger的创建者Yuri Shkuro还出版了一本书Mastering Distributed Tracing,该书深入介绍了Jaeger设计和操作的各个方面,以及一般的分布式跟踪。

特征

  • OpenTracing兼容的数据模型和工具库
  • 对每个服务/端点使用一致的前期采样概率
  • 多个存储后端:Cassandra,Elasticsearch,内存。
  • 系统拓扑图形
  • 自适应采样(即将推出)
  • 收集后数据处理管道(即将推出)

您的应用程序必须经过检测,然后才能将跟踪数据发送到Jaeger后端。有关如何使用OpenTracing API以及如何初始化和配置Jaeger追踪器的信息,后面客户端库部分会讲解。

一体化

All-in-one是为快速本地测试而设计的可执行文件,使用内存存储组件启动Jaeger UI、收集器、查询和代理。启动一体化的最简单方法是使用发布到DockerHub的预构建映像

$ 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 14250:14250 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.19

然后,您可以导航到http://localhost:16686以访问Jaeger UI。

架构

Jaeger的客户端遵循OpenTracing标准中描述的数据模型。

  • Span

Span表示Jaeger中的逻辑工作单元,具有操作名称,操作的开始时间和持续时间。 Span可以嵌套并排序以建立因果关系模型。

  • Trace

跟踪是系统中的数据/执行路径,可以将其视为Span的有向无环图。

组件

Jaeger既可以部署为一体式二进制文件,其中所有Jaeger后端组件都运行在单个进程中,也可以部署为可扩展的分布式系统,如下所述。有两个主要的部署选项:

  • 收集器直接写入存储。
  • 收集器写到Kafka作为初步缓冲。

直接写入存储

写到Kafka作为初步缓冲

1.客户端库

aeger客户端是OpenTracing API的特定语言实现。它们可用于检测应用程序以进行分布式跟踪,无论是手动还是现有的各种开源框架,如Flask, Dropwizard, gRPC,以及更多,这些都已经集成到OpenTracing中了。

检测服务在接收新请求时创建span,并将上下文信息(trace ID, span ID和 baggage)附加到传出请求。只有ids 和baggage随请求一起传播; 所有其他概要分析数据(如操作名称,时间,标签和日志)都不会传播。相反,它在后台异步地传输到Jaeger后端。

该仪器被设计成在生产过程中始终处于开启状态。为了最大程度地减少开销,Jaeger客户采用了各种采样策略。当跟踪被采样时,概要跨度数据被捕获并传输到Jaeger后端。如果没有对跟踪进行采样,则根本不会收集任何分析数据,并且会短路对opentrace api的调用,从而导致最小的开销。默认情况下,Jaeger客户端对0.1%的迹线进行采样(每1000条中的1条),并且能够从Jaeger后端检索采样策略。

上下文传播的说明

2.Agent

Jaeger代理是一个网络守护程序,它侦听通过UDP发送的跨度,并将其分批发送给收集器。它被设计为作为基础设施组件部署到所有主机上。代理将收集器的路由和发现从客户端抽象出来。

3.Collector

Jaeger收集器从Jaeger代理接收跟踪,并通过处理管道运行它们。当前,我们的管道会验证跟踪,为其建立索引,执行任何转换并最终存储它们.Jaeger的存储是一个可插拔组件,目前支持Cassandra, Elasticsearch和Kafka。

4.Query

查询是一种服务,它从存储中检索跟踪,并托管一个UI来显示它们。

5.Ingester

Ingester是一个读取Kafka topic并写入到另一个存储后端的服务(Cassandra, Elasticsearch).

客户端采样配置

当使用配置对象实例化一个跟踪器时,采样类型可以通过sampler.typesampler.param属性进行选择,Jaeger库支持如下采样:

  • Constant(sampler.type=const) 采样器总是对所有的traces做出相同的决定。它要么采集全部traces(sampler.param=1)要么都不采集(sampler.param=0
  • Probabilistic(sampler.type=probabilistic)采样器做出随机采样决策,采样概率等于sampler.param属性的值。例如,如果sampler.param=0.1表明大约有1/10的traces会被采样
  • Rate Limiting(sampler.type=ratelimiting)采样器使用漏桶速率限制器,以确保以一定的恒定速率采样痕迹。例如,如果sampler.param=2.0它将以每秒2条跟踪的速率对请求进行采样。
  • Romote(sampler.type=remote,默认值)采样器向Jaeger代理咨询在当前服务中使用的适当采样策略。这允许从Jaeger后端的中央配置甚至动态地控制服务中的采样策略

客户端库

所有Jaeger客户端库都支持OpenTracing API。该页面的其余部分包含有关在已经使用OpenTracing API进行了测试的应用程序中配置和实例化Jaeger跟踪器的信息。

1.初始化Jaeger Tracer

每种语言的初始化语法略有不同,通用模式是不显式创建Tracer,而是使用Configuration类来执行此操作。配置允许对Tracer进行更简单的参数化,例如更改默认采样器或Jaeger代理的位置。

func NewTracer(servicename string, addr string) (opentracing.Tracer, io.Closer, error) {
	cfg := jaegercfg.Configuration{
		ServiceName: servicename,
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		}, // 采样策略
		Reporter: &jaegercfg.ReporterConfig{
			LogSpans:            true,
			BufferFlushInterval: 1 * time.Second,
		}, // Reporter
	}

	sender, err := jaeger.NewUDPTransport(addr, 0)
	if err != nil {
		return nil, nil, err
	}

	reporter := jaeger.NewRemoteReporter(sender)
	tracer, closer, err := cfg.NewTracer(
		jaegercfg.Reporter(reporter),
	)

	return tracer, closer, err
}

Jaeger追踪器使用Reporter处理完成的跨度。通常,Jaeger 库附带以下Reporter:

  • NullReporter,不对span做任何操作,在单元测试时会有用。
  • LoggingReporter,只需记录span已结束的事实,通常会打印trace ID和span ID以及操作名称
  • CompositeReporter,列出其他reporter的列表,并逐个调用它们。
  • RemoteReporter(默认值),在内存中缓冲一定数量的完成span,并使用发送方将一批span跨进程提交给Jaeger后端。发送方负责将span序列化为有线格式(例如:Thrift或者JSON)并与后端组件通信(例如:通过 UDP 或者HTTP).

默认情况下,Jaeger库使用UDP发送器将完成的跨度报告给jaeger-agent守护程序。默认的最大数据包大小为65,000字节,当通过环回接口连接到代理时,可以在没有分割的情况下传输。但是,某些操作系统(尤其是MacOS)会限制UDP数据包的最大缓冲区大小,如果遇到EMSGSIZE错误的问题,请考虑提高内核中的限制。您也可以配置客户端库使用较小的最大包大小,但如果您有较大的跨度,例如,如果您记录大块的数据,这可能会导致问题。客户端丢弃超过最大数据包大小的span.另一种选择是使用非UDP传输.

指标

Jaeger追踪器会发出各种指标,说明它们已经开始和完成了多少跨度或追踪,其中有多少采样或不采样,如果在将跟踪上下文从入站请求或报告跨度解码到后端时有任何错误。

传播格式

当SpanContext被编码为对另一服务的请求的一部分时,Jaeger客户端库默认为下面指定的Jaeger本机传播格式。此外,Jaeger客户端支持Zipkin B3格式W3C Trace-Context

  1. Trace/Span 标识

key

uber-trace-id

  • 在HTTP不区分大小写
  • 在协议中小写保留header case

value

{trace-id}:{span-id}:{parent-span-id}:{flags}

  • {trace-id}:

    • 64位或者128位随机数
    • 可以是可变长度,较短的值在左侧填充0
    • 某些语言的客户端支持128位
    • 0值非法
  • {span-id}:

    • 64位随机数
    • 0值非法
  • {parent-span-id}:

    • 64位值代表父span id
    • 已过期,在需要jaeger客户端库中接收端已经不再使用,但是发送端依然包含它
    • 0值有效,标识是root span
  • {flags}

    • 一个字节的位图,为两个十六进制数字
    • 第1位(最右、最不重要的位掩码0x01)是采样标志:值为1表示跟踪已采样,建议所有下游服务都遵守。0表示跟踪未被取样,建议所有下游服务遵循这一原则
    • 第2位(位掩码0x02)是调试标志:仅当设置了采样标志时,才应设置调试标志,指示后端尽量不删除此跟踪
    • 第3位(位掩码0x04)没有使用
    • 位4(位掩码0x08)是“ firehose”标志:标记为“ firehose”的跨度不在存储中索引,只能通过跟踪ID来检索跟踪
    • 其他位是未使用的

2.Baggage

Key:uberctx-{baggage-key} Value: url编码的字符串 限定: 由于HTTP标头不能保留大小写,因此Jaeger建议将行李密钥设置为小写,例如kebab-case,例如my-baggage-key-1

示例:以下代码序列

span.SetBaggageItem("key1", "value1")
span.SetBaggageItem("key2", "value2")

将产生以下HTTP标头:

uberctx-key1: value1
uberctx-key2: value2