Spring Cloud系列(十四):Sleuth 分布式跟踪原理分析

转自:

http://www.gxitsky.com/2019/05/01/springcloud-14-tracing-sleuth-zipkin-principle/

Sleuth 通过 traceId 实现了对分布式系统调用链路的跟踪。在一次服务请求链路中,会保持并传递一个 traceId,从而将不同服务的请求跟踪信息串联起来,不同服务的 traceId 相同表示处在同一请求链中。

  基于 HTTP 请求的数据传递有两种方式:一种是做为参数传递,另一种是做为头信息传递。而 Sleuth 的 traceId 属于附加信息,不参与实际的业务,所以做为参数传递并不合适,实际也是作为头信息来传递的。

Request Header

参考 Spring Cloud系列(十三):分布式服务链路跟踪 Sleuth 中的示例项目,Sleuth 会在请求的 Header 中增加实现跟踪需的信息,给远程调用接口打上断点,使用 request.getHeaderNames() 取出所有头信息。

Headers

1
["x-b3-spanid","x-b3-parentspanid","x-b3-sampled","x-b3-traceid","accept","user-agent","host","connection"]

springcloud-14-sleuth-request-header-traceid.png

 

可以看到,在请求头信息中多了 4 个属性:

  • x-b3-spanid:一个工作单元(rpc 调用)的唯一标识。
  • x-b3-parentspanid:当前工作单元的上一个工作单元,Root Span(请求链路的第一个工作单元)的值为空。
  • x-b3-traceid:一条请求链条(trace) 的唯一标识。
  • x-b3-sampled:是否被抽样为输出的标志,1 为需要被输出,0 为不需要被输出。

日志跟踪接入

Sleuth 会把跟踪数据 (appname、traceId、spanId、exportable) 添加到 Slf4J MDC 中,因此您可以从日志聚合器中的给定跟踪或跨度中提取所有日志,如以下示例日志中所示:

1
2
3
2016-02-02 15:30:57.902  INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:31:01.936 INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...

MDC 的实现实际是将需要记录到日志的信息设置到当前线程的上下文(ThreadContext)中。

MDC 中的信息:[appname,traceId,spanId,exportable]

  • appname:应用名称,即 spring.application.name 的值。
  • tranceId:整个请求链路的唯一ID。
  • spanId:基本的工作单元,一个 RPC 调用就是一个新的 span。启动跟踪的初始 span 称为 root span ,此 spanId 的值与 traceId 的值相同。
  • exportable:是否将数据导入到 Zipkin 中,true 表示导入成功,false 表示导入失败。

Sleuth 跟踪原理

分布式系统中的服务调用链路跟踪在理论上并不复杂,主要有个关键点,一个是为请求链路创建唯一跟踪标识,二个统计各个处理单元的延迟时间。

  1. 为了实现请求链路跟踪,当请求发送到分布式系统的入口时,只需要在服务跟踪框架为该请求创建唯一的跟踪标识,并保证该标识在在分布式系统内部流转,直到返回请求为止。该标识即为 traceId,通过该标识,就能将不同服务调用的日志串联起来。
  2. 为了统计各处理单元(应用服务)的延迟,当请求到达或处理逻辑达到某个状态时,也通过一个唯一标识来标记开始、具体过程及结束(标识一个服务内请求进入、处理到结束),该标识即为 spanId。对于每个 spanId 来说,必须有开始和结束两个节点,通过计算开始 span 和 结束 span 的时间戳,就能统记出该 span 的时间延迟。

Sleuth 采样比例

跟踪信息收集默认是 0.1(10%) 的采样比例,可通过 probability 属性修改;或可采用每秒速率来控制采集数据,属性是 rate。

1
2
3
4
# 跟踪信息收集采样比例,默认 0.1,为 1 是即 100%,收集所有
spring.sleuth.sampler.probability=1
# 每秒速率,即每秒最多能跟踪的请求,rate 优先
spring.sleuth.sampler.rate=50

Brave 分布式跟踪

从版本 2.0.0 开始,Spring Cloud Sleuth 使用 Brave 作为跟踪库。 因此,Sleuth 不再负责存储上下文,而是将该工作委托给 Brave。
由于 Sleuth 与 Brave 有不同的命名和标记惯例,我们决定从现在开始遵循 Brave 的惯例。 但是,如果要使用传统的侦听方法,可以将 spring.sleuth.http.legacy.enabled 属性设置为 true

Span 上下文传播

Span Context(上下文) 必须传播到跨进程边界的任何 子 Span 状态。Span Context 还是一部分是 Baggage。Trace ID 和 Span ID 是 Span Context 必需的,Baggage 是可选的。

Baggage 是一组存储在 Span Context 中的 key:value(键值对)。Baggage 与 Traceg 一起移动并附在每个 Span 上。Spring Cloud Sleuth 可以识别以 baggage- 为前缀的 HTTP 头,这是与 baggage 相关的头,消息传递以 baggage_ 开始。

目前,Baggage 的数量和大小没有限制。但是,太多会降低系统吞吐量或增加 RPC 延迟。在极端情况下,过多的 Baggage 会导致应用程序崩溃,因为超过了传输级别的消息或报头容量。

在 Span Context 中设置 Baggage 示例:

1
2
3
Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");

Baggage 与 Span Tags

Baggage 跟随 Trace 一起移动(每个子 span 都包含 父 span 的 Baggage)。Zipkin 不知道 baggage ,也不接收这些信息。

注意:从 Sleuth 2.0.0 开始,必须在项目配置中明确传递 baggage 钥匙名称。

Tags 附加到指定的 span ,也就是该标签只在指定的 span 呈现。但是,如果包含 Tag 的 span 存在,则可以根据 Tag 搜索 trace

如果希望能够根据 baggage 查找 span*,则应在 *root span 中添加相应的元素作为 Tag

baggage 集成示例:

1
2
spring.sleuth.baggage-keys=baz,bizarrecase
spring.sleuth.propagation-keys=foo,upper_case
1
2
initialSpan.tag("foo",ExtraFieldPropagation.get(initialSpan.context(), "foo"));
initialSpan.tag("UPPER_CASE",ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));
posted @ 2020-07-30 11:35  adaandy  阅读(7268)  评论(0编辑  收藏  举报