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:/start
span。它具有服务器已接收(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
而不是application
property 文件中传递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.name
property。
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
依赖项。