On this page
48. 简介
Spring Cloud Sleuth 为Spring Cloud实现了分布式跟踪解决方案。
48.1 术语
Spring Cloud Sleuth 借用小巧玲珑术语。
跨度:基本工作单位。例如,发送 RPC 是一个新的 span,就像向 RPC 发送响应一样。 Spans 由 span 的唯一 64-bit ID 和 span 所属的跟踪的另一个 64-bit ID 标识。 Spans 还有其他数据,例如描述,timestamped events,key-value annotations(标签),导致它们的 span 的 ID,以及 process ID(通常是 IP 地址)。
Spans 可以启动和停止,并跟踪他们的时间信息。创建 span 后,必须在将来某个时候停止它。
启动跟踪的初始 span 称为
root span。 span 的 ID 的 value 等于跟踪 ID。
**跟踪:**一组构成 tree-like 结构的 spans。例如,如果您运行分布式 big-data store,则可能会由PUT请求形成跟踪。
**注释:**用于记录 time 中 event 的存在。使用勇敢检测,我们不再需要为Zipkin设置特殊的 events 来了解 client 和服务器是谁,请求开始的位置以及结束的位置。然而,出于学习目的,我们标记这些事件以突出发生了什么样的动作。
cs:Client 已发送。 client 提出了请求。这个 annotation 指示 span 的开始。
sr:服务器收到:服务器端获得请求并开始处理它。从此时间戳中减去
cs时间戳会显示网络延迟。ss:服务器已发送。在请求处理完成时注释(当响应被发送回 client 时)。从此时间戳中减去
sr时间戳会显示服务器端处理请求所需的 time。cr:Client 收到了。表示 span 的结束。 client 已成功收到服务器端的响应。从此时间戳中减去
cs时间戳会显示 client 从服务器接收响应所需的整个 time。
下图显示了Span和Trace在系统中的外观以及 Zipkin annotations:

音符的每种颜色表示 span(有七个 spans - 从A到G)。请考虑以下注释:
Trace Id = X
Span Id = D
Client Sent
此注释表示当前 span 将Trace Id设置为X,Span Id设置为D。此外,发生了Client Sent event。
下图显示了 spans 的 parent-child 关系:

48.2 目的
以下部分涉及上图中显示的 example。
48.2.1 使用 Zipkin 进行分布式跟踪
这个 example 有七个 spans。如果您转到 Zipkin 中的跟踪,您可以在第二个跟踪中看到此数字,如下图所示:

但是,如果选择特定跟踪,则可以看到四个 spans,如下图所示:

选择特定跟踪时,会看到合并的 spans。这意味着,如果有两个 spans 发送到 Zipkin,服务器已接收和服务器已发送或 Client 已接收和 Client 已发送注释,则它们将显示为单个 span。
在这种情况下,为什么七个和四个 spans 之间存在差异?
两个 spans 来自
http:/startspan。它具有服务器已接收(sr)和服务器已发送(ss)注释。两个 spans 来自从
service1到service2到http:/foo端点的 RPC 调用。 Client 已发送(cs)和 Client 已接收(cr)events 发生在service1端。服务器已接收(sr)和服务器已发送(ss)事件发生在service2端。这两个 spans 形成一个与 RPC 调用相关的逻辑 span。两个 spans 来自从
service2到service3到http:/bar端点的 RPC 调用。 Client 已发送(cs)和 Client 已接收(cr)events 发生在service2端。服务器已接收(sr)和服务器已发送(ss)事件发生在service3端。这两个 spans 形成一个与 RPC 调用相关的逻辑 span。两个 spans 来自从
service2到service4到http:/baz端点的 RPC 调用。 Client 已发送(cs)和 Client 已接收(cr)events 发生在service2端。服务器已接收(sr)和服务器已发送(ss)事件发生在service4端。这两个 spans 形成一个与 RPC 调用相关的逻辑 span。
所以,如果我们计算物理 spans,我们有一个来自http:/start,两个来自service1调用service2,两个来自service2调用service3,两个来自service2调用service4。总之,我们共有七个 spans。
从逻辑上讲,我们看到了四个 Spans 的信息,因为我们有一个与service1的传入请求相关的 span 和与 RPC calls 相关的三个 spans。
48.2.2 可视化错误
Zipkin 允许您可视化跟踪中的错误。抛出 exception 并且没有被捕获时,我们在 span 上设置了正确的标签,然后 Zipkin 可以正确着色。您可以在跟踪列表中看到一条红色的迹线。这看起来是因为抛出了 exception。
如果单击该跟踪,则会看到类似的图片,如下所示:

如果您随后单击其中一个 spans,则会看到以下内容

span 显示错误的原因以及与之相关的整个堆栈跟踪。
48.2.3 使用 Brave 进行分布式跟踪
从 version 2.0.0开始,Spring Cloud Sleuth 使用勇敢作为跟踪 library。因此,Sleuth 不再负责存储 context,而是将其工作委托给 Brave。
由于 Sleuth 具有与 Brave 不同的命名和标记约定,因此我们决定从现在开始遵循 Brave 的约定。但是,如果要使用 legacy Sleuth 方法,可以将spring.sleuth.http.legacy.enabled property 设置为true。
48.2.4 实例
图 1_.单击 Pivotal Web Services 图标以查看它!

Zipkin 中的依赖关系图应类似于以下图像:

图 1_.单击 Pivotal Web Services 图标以查看它!

48.2.5 Log 相关
当使用 grep 通过扫描等于(对于 example)2485ec27856c56f4的跟踪 ID 来读取这四个 applications 的日志时,您将获得类似于以下内容的输出:
service1.log:2016-02-26 11:15:47.561 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Hello from service1. Calling service2
service2.log:2016-02-26 11:15:47.710 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Hello from service2. Calling service3 and then service4
service3.log:2016-02-26 11:15:47.895 INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application : Hello from service3
service2.log:2016-02-26 11:15:47.924 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service3 [Hello from service3]
service4.log:2016-02-26 11:15:48.134 INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application : Hello from service4
service2.log:2016-02-26 11:15:48.156 INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application : Got response from service4 [Hello from service4]
service1.log:2016-02-26 11:15:48.182 INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]
如果使用 log 聚合工具(例如Kibana,Splunk和其他),则可以对发生的 events 进行排序。来自 Kibana 的示例将类似于以下图像:

如果要使用Logstash,以下列表显示 Logstash 的 Grok pattern:
filter {
# pattern matching logback pattern
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
}
如果要将 Grok 与 Cloud Foundry 中的日志一起使用,则必须使用以下 pattern:
filter {
# pattern matching logback pattern
grok {
match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
}
使用 Logstash JSON Logback
通常,您不希望将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中。为此,您必须执行以下操作(为了便于阅读,我们以groupId:artifactId:version表示法传递依赖项)。
依赖关系设置
确保 Logback 位于 classpath(
ch.qos.logback:logback-core)上。添加 Logstash Logback 编码。对于 example,要使用 version
4.6,请添加net.logstash.logback:logstash-logback-encoder:4.6。
Logback Setup
请考虑以下 Logback configuration 文件的示例(名为logback-spring.xml)。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- Example for logging into the build folder of your project -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
<!-- You can override this to have a custom pattern -->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file -->
<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- Appender to log to file in a JSON format -->
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"severity": "%level",
"service": "${springAppName:-}",
"trace": "%X{X-B3-TraceId:-}",
"span": "%X{X-B3-SpanId:-}",
"parent": "%X{X-B3-ParentSpanId:-}",
"exportable": "%X{X-Span-Export:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
<!-- uncomment this to have also JSON logs -->
<!--<appender-ref ref="logstash"/>-->
<!--<appender-ref ref="flatfile"/>-->
</root>
</configuration>
那个 Logback configuration 文件:
将来自 application 的信息以 JSON 格式记录到
build/${spring.application.name}.json文件。已注释掉两个额外的 appender:console 和标准 log 文件。
具有与上一节中介绍的相同的 logging pattern。
如果使用自定义
logback-spring.xml,则必须在bootstrap而不是applicationproperty 文件中传递spring.application.name。否则,您的自定义 logback 文件无法正确读取 property。
48.2.6 传播 Span Context
span context 是 state,它必须传播到跨 process 边界的任何子 spans。 Span Context 的一部分是 Baggage。 trace 和 span ID 是 span context 的必需部分。 Baggage 是一个可选部分。
Baggage 是存储在 span context 中的一组 key:value 对。 Baggage 与痕迹一起旅行,并附在每个 span 上。 Spring Cloud Sleuth 了解如果 HTTP 标头以baggage-为前缀,则标头为 baggage-related,对于消息传递,它以baggage_开头。
目前对 baggage 物品的数量或大小没有限制。但是,请记住,太多可能会降低系统吞吐量或增加 RPC 延迟。在极端情况下,由于超过 transport-level 消息或标头容量,过多的 baggage 可能会导致 application 崩溃。
以下 example 显示在 span 上设置 baggage:
Span initialSpan = this.tracer.nextSpan().name("span").start();
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(initialSpan)) {
ExtraFieldPropagation.set("foo", "bar");
ExtraFieldPropagation.set("UPPER_CASE", "someValue");
}
Baggage 与 Span 标签
Baggage 带着痕迹旅行(每个孩子 span 都包含 parent 的行李)。 Zipkin 不了解 baggage 并且不接收该信息。
从 Sleuth 2.0.0 开始,您必须在项目 configuration 中显式传递 baggage key 名称。阅读有关该设置的更多信息这里
标签附加到特定的 span。换句话说,它们仅针对特定的 span 呈现。但是,您可以按标记搜索以查找跟踪,假设存在搜索标记 value 的 span。
如果您希望能够基于 baggage 查找 span,则应在根 span 中添加相应的条目作为标记。
span 必须在范围内。
以下清单显示了使用 baggage 的 integration 测试:
设置.
spring.sleuth:
baggage-keys:
- baz
- bizarrecase
propagation-keys:
- foo
- upper_case
代码.
initialSpan.tag("foo",
ExtraFieldPropagation.get(initialSpan.context(), "foo"));
initialSpan.tag("UPPER_CASE",
ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));
48.3 将 Sleuth 添加到项目中
本节介绍如何使用 Maven 或 Gradle 将 Sleuth 添加到项目中。
要确保在 Zipkin 中正确显示 application name,请在
bootstrap.yml中设置spring.application.nameproperty。
48.3.1 只 Sleuth(log 相关)
如果您只想使用 Spring Cloud Sleuth 而不使用 Zipkin integration,请将spring-cloud-starter-sleuth模块添加到项目中。
以下 example 显示了如何使用 Maven 添加 Sleuth:
Maven 的.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-sleuth。
以下 example 显示了如何使用 Gradle 添加 Sleuth:
摇篮.
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-sleuth"
}
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-sleuth。
通过 HTTP 使用 Zipkin 48.3.2 Sleuth
如果您同时需要 Sleuth 和 Zipkin,请添加spring-cloud-starter-zipkin依赖项。
以下 example 显示了如何为 Maven 执行此操作:
Maven 的.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-zipkin。
以下 example 显示了如何为 Gradle 执行此操作:
摇篮.
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-zipkin。
48.3.3 Sleuth 与 Zipkin over RabbitMQ 或 Kafka
如果要使用 RabbitMQ 或 Kafka 而不是 HTTP,请添加spring-rabbit或spring-kafka依赖项。默认目标 name 是zipkin。
如果使用 Kafka,则必须相应地设置 property spring.zipkin.sender.type property:
spring.zipkin.sender.type: kafka
spring-cloud-sleuth-stream已弃用且与这些目标不兼容。
如果你想在 RabbitMQ 上使用 Sleuth,请添加spring-cloud-starter-zipkin和spring-rabbit依赖项。
以下 example 显示了如何为 Gradle 执行此操作:
Maven 的.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${release.train.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-zipkin。这样,所有嵌套的依赖项都会被下载。
要自动配置 RabbitMQ,请添加
spring-rabbit依赖项。
摇篮.
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
compile "org.springframework.amqp:spring-rabbit"
}
我们建议您通过 Spring BOM 添加依赖项管理,这样您就无需自行管理版本。
将依赖项添加到
spring-cloud-starter-zipkin。这样,所有嵌套的依赖项都会被下载。
要自动配置 RabbitMQ,请添加
spring-rabbit依赖项。