共计 12433 个字符,预计需要花费 32 分钟才能阅读完成。
Spring Cloud Sleuth 介绍
Spring Cloud Sleuth 为 Spring Cloud 实现了分布式跟踪解决方案。
术语
Spring Cloud Sleuth 借用了 Dapper 的术语。
Span:基本工作单元,例如,发送 RPC 是一个新的 span,就像向 RPC 发送响应一样,Span 由 span 的唯一 64 位 ID 标识,另一个 64 位 ID 标识其所属的 Trace。Span 还有其他数据,例如描述、带时间戳的事件、键值 annotations(标签),导致它们的 span 的 ID 以及进程 ID(通常是 IP 地址)。
span 可以启动和停止,它们可以跟踪自己的时间信息,创建 span 后,必须在将来的某个时刻停止它。
启动 Trace 的初始 span 称为
root span
,该 span 的 ID 值等于 trace ID。
Trace:一组 span 形成的树状结构,例如,如果运行分布式大数据存储,则可能由 PUT
请求形成 trace。
Annotation:用于及时记录事件的存在,使用 Brave 工具,不再需要为 Zipkin 设置特殊的事件来了解客户端和服务器是谁、请求在哪里开始以及在哪里结束,然而,出于学习目的,标记这些事件以突出发生了什么类型的操作。
- cs:Client Sent,客户端发起了一个请求,这个 annotation 表示 span 的开始。
-
sr:Server Received,服务器端获得了请求并开始处理它,从此时间戳中减去
cs
时间戳会显示网络延迟。 -
ss:Server Sent,在请求处理完成时注释(当响应被发送回客户端时),从此时间戳中减去
sr
时间戳会显示服务器端处理请求所需的时间。 -
cr:Client Received,表示 span 的结束,客户端已成功从服务器端收到响应,从此时间戳中减去
cs
时间戳会显示客户端从服务器接收响应所需的全部时间。
下图显示了系统中的 Span 和Trace,以及 Zipkin annotations:
标记的每种颜色表示一个 span(有七个 span — 从 A 到G),请考虑以下标记:
Trace Id = X
Span Id = D
Client Sent
此标记表示当前 span 的 Trace Id 设置为 X,Span Id 设置为 D,此外,还发生了Client Sent
事件。
下图显示了 span 的父—子关系:
用途
以下部分参考上图中显示的示例。
使用 Zipkin 进行分布式跟踪
这个例子有七个 span,如果你进入 Zipkin 中的 trace,你可以在第二个 trace 中看到这个数字,如下图所示:
但是,如果选择特定 trace,则可以看到四个 span,如下图所示:
选择特定 trace 时,你会看到合并的 span,这意味着,如果通过 Server Received 和 Server Sent 或 Client Received 和 Client Sent annotations 向 Zipkin 发送了两个 span,则它们将显示为单个 span。
在这种情况下,为什么七个和四个 span 之间存在差异?
- 一个 span 来自
http:/start
span,它具有 Server Received(sr
)和 Server Sent(ss
)annotations。 - 两个 span 来自从
service1
到service2
的http:/foo
端点的 RPC 调用,Client Sent(cs
)和 Client Received(cr
)事件发生在service1
端,Server Received(sr
)和 Server Sent(ss
)事件发生在service2
端,这两个 span 形成一个与 RPC 调用相关的逻辑 span。 - 两个 span 来自从
service2
到service3
的http:/bar
端点的 RPC 调用,Client Sent(cs
)和 Client Received(cr
)事件发生在service2
端,Server Received(sr
)和 Server Sent(ss
)事件发生在service3
端,这两个 span 形成一个与 RPC 调用相关的逻辑 span。 - 两个 span 来自从
service2
到service4
的http:/baz
端点的 RPC 调用,Client Sent(cs
)和 Client Received(cr
)事件发生在service2
端,Server Received(sr
)和 Server Sent(ss
)事件发生在service4
端,这两个 span 形成一个与 RPC 调用相关的逻辑 span。
因此,如果我们计算物理 span,我们有一个来自 http:/start
,两个来自service1
调用 service2
,两个来自service2
调用 service3
,两个来自service2
调用service4
,总之,我们总共有七个 span。
从逻辑上讲,我们看到四个总 Span 的信息,因为我们有一个与传入请求到 service1
相关的 span 和三个与 RPC 调用相关的 span。
可视化错误
Zipkin 允许你可视化 trace 中的错误,当抛出异常而没有被捕获时,在 span 上设置了适当的标签,然后 Zipkin 可以正确地着色,你可以在 trace 列表中看到一条红色的 trace,出现这种情况是因为抛出了异常。
如果单击该 trace,则会看到类似的图片,如下所示:
如果你随后单击其中一个 span,则会看到以下内容:
span 显示错误的原因以及与之相关的整个堆栈跟踪。
Brave 的分布式跟踪
从版本 2.0.0
开始,Spring Cloud Sleuth 使用 Brave 作为跟踪库,因此,Sleuth 不再负责存储上下文,而是将该工作委托给 Brave。
由于 Sleuth 与 Brave 有不同的命名和标记约定,Spring 决定从现在开始遵循 Brave 的约定,但是,如果要使用遗留的 Sleuth 方法,可以将 spring.sleuth.http.legacy.enabled
属性设置为true
。
实例
点击这里查看实例!
Zipkin 中的依赖关系图应类似于以下图像:
日志相关
当使用 grep 通过扫描等于(例如)2485ec27856c56f4
的 trace ID 来读取这四个应用程序的日志时,你将获得类似于以下内容的输出:
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]]
如果你使用日志聚合工具(例如 Kibana、Splunk 等),你可以排序发生的事件,Kibana 的一个例子类似于下图:
如果要使用 Logstash,以下列表显示 Logstash 的 Grok 模式:
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 中的日志一起使用,则必须使用以下模式:
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}" }
}
}
JSON 化 Logback 与 Logstash 一起使用
通常,你不希望将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中,为此,你必须执行以下操作(为了便于阅读,在groupId:artifactId:version
notation 中传递依赖项)。
依赖关系设置
- 确保 Logback 位于类路径上(
ch.qos.logback:logback-core
)。 - 添加 Logstash Logback encode,例如,要使用版本 4.6,请添加
net.logstash.logback:logstash-logback-encoder:4.6
。
Logback 设置
请考虑以下 Logback 配置文件示例(名为 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 配置文件:
- 将来自应用程序的信息以 JSON 格式记录到
build/${spring.application.name}.json
文件中。 - 已注释掉两个额外的 appender:控制台和标准日志文件。
- 具有与上一节中介绍的相同的日志记录模式。
如果使用自定义
logback-spring.xml
,则必须在bootstrap
而不是application
属性文件中传递spring.application.name
,否则,你的自定义 logback 文件无法正确读取该属性。
传播 span 上下文
span 上下文是必须传播到跨进程边界的任何子 span 的状态,span 上下文的一部分是 Baggage,trace 和 span ID 是 span 上下文的必需部分,Baggage 是可选部分。
Baggage 是一组存储在 span 上下文中的键:值对,Baggage 随着 trace 一起移动,并附在每一个 span 上,Spring Cloud Sleuth 了解如果 HTTP header 以 baggage-
为前缀,则标题与行李相关,并且对于消息,它以 baggage_
开头。
目前对 baggage 条目的数量或大小没有限制,但是,请记住,太多可能会降低系统吞吐量或增加 RPC 延迟,在极端情况下,由于超出传输级别的消息或 header 容量,过多的 baggage 可能会使应用程序崩溃。
以下示例显示了在 span 上设置 baggage:
Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");
Baggage 与 Span 标签
Baggage 随 trace 而行(每个子 span 包含其父级的 baggage),Zipkin 不知道 baggage,也不接收这些信息。
从 Sleuth 2.0.0 开始,你必须在项目配置中明确传递 baggage 键名称。
标签附加到特定 span,换句话说,它们仅针对特定 span 呈现,但是,你可以按标记搜索以查找 trace,假设存在具有搜索标记值的 span。
如果你希望能够根据 baggage 查找 span,则应在根 span 中添加相应的条目作为标记。
span 必须在 scope 内。
以下清单显示了使用 baggage 的集成测试:
设置
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"));
添加 Sleuth 到项目
本节介绍如何使用 Maven 或 Gradle 将 Sleuth 添加到项目中。
要确保你的应用程序名称在 Zipkin 中正确显示,请在
bootstrap.yml
中设置spring.application.name
属性。
只有 Sleuth(log 相关)
如果你想在没有 Zipkin 集成的情况下仅使用 Spring Cloud Sleuth,请将 spring-cloud-starter-sleuth
模块添加到你的项目中。
以下示例显示如何使用 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
。
以下示例显示如何使用 Gradle 添加 Sleuth:
Gradle
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 Sleuth 与 Zipkin 一起使用
如果你想要 Sleuth 和 Zipkin,请添加 spring-cloud-starter-zipkin
依赖项。
以下示例显示了如何为 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>
以下示例显示了如何为 Gradle 执行此操作:
Gradle
dependencyManagement {
imports {mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {compile "org.springframework.cloud:spring-cloud-starter-zipkin"}
在 RabbitMQ 或 Kafka 上 Sleuth 与 Zipkin 一起使用
如果你想使用 RabbitMQ 或 Kafka 而不是 HTTP,添加 spring-rabbit
或spring-kafka
依赖项,默认目标名称是zipkin
。
如果使用 Kafka,则必须相应地设置属性 spring.zipkin.sender.type
属性:
spring.zipkin.sender.type: kafka
spring-cloud-sleuth-stream
已弃用且与这些目标不兼容。
如果你想在 RabbitMQ 上使用 Sleuth,请添加 spring-cloud-starter-zipkin
和spring-rabbit
依赖项。
以下示例显示了如何为 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>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
- 建议你通过 Spring BOM 添加依赖关系管理,这样你就无需自行管理版本。
- 添加依赖
spring-cloud-starter-zipkin
,这样,所有嵌套的依赖项都会被下载。 - 要自动配置 RabbitMQ,请添加
spring-rabbit
依赖项。
Gradle
dependencyManagement { 1
imports {mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-starter-zipkin" 2
compile "org.springframework.amqp:spring-rabbit" 3
}
覆盖 Zipkin 的自动配置
Spring Cloud Sleuth 从 2.1.0 版开始支持向多个跟踪系统发送 trace,为了使其工作,每个跟踪系统都需要有一个 Reporter<Span>
和Sender
,如果要覆盖提供的 bean,则需要为它们指定一个特定的名称,为此,你可以分别使用 ZipkinAutoConfiguration.REPORTER_BEAN_NAME
和ZipkinAutoConfiguration.SENDER_BEAN_NAME
。
@Configuration
protected static class MyConfig {@Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME)
Reporter<zipkin2.Span> myReporter() {return AsyncReporter.create(mySender());
}
@Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
MySender mySender() {return new MySender();
}
static class MySender extends Sender {
private boolean spanSent = false;
boolean isSpanSent() {return this.spanSent;}
@Override
public Encoding encoding() {return Encoding.JSON;}
@Override
public int messageMaxBytes() {return Integer.MAX_VALUE;}
@Override
public int messageSizeInBytes(List<byte[]> encodedSpans) {return encoding().listSizeInBytes(encodedSpans);
}
@Override
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
this.spanSent = true;
return Call.create(null);
}
}
}
额外的资源
你可以点击这里观看 Reshmi Krishna 和 Marcin Grzejszczak 关于 Spring Cloud Sleuth 和 Zipkin 的视频。
你可以在 openzipkin/sleuth-webmvc-example 存储库中检查 Sleuth 和 Brave 的不同设置。