Spring Cloud Sleuth为Spring Cloud实现了分布式追踪解决方案。css
Spring Cloud Sleuth借用了Dapper的术语。html
Span:基本工做单元,例如,发送RPC是一个新的span,就像向RPC发送响应同样,Span由span的惟一64位ID标识,另外一个64位ID标识其所属的Trace。Span还有其余数据,例如描述、带时间戳的事件、键值annotations(标签),致使它们的span的ID以及进程ID(一般是IP地址)。git
span能够启动和中止,它们能够追踪本身的时间信息,建立span后,必须在未来的某个时刻中止它。github
启动Trace的初始span称为
root span
,该span的ID值等于trace ID。
Trace:一组span造成的树状结构,例如,若是运行分布式大数据存储,则可能由PUT
请求造成trace。web
Annotation:用于及时记录事件的存在,使用Brave工具,再也不须要为Zipkin设置特殊的事件来了解客户端和服务器是谁、请求在哪里开始以及在哪里结束,然而,出于学习目的,标记这些事件以突出发生了什么类型的操做。spring
cs
时间戳会显示网络延迟。sr
时间戳会显示服务器端处理请求所需的时间。cs
时间戳会显示客户端从服务器接收响应所需的所有时间。下图显示了系统中的Span和Trace,以及Zipkin annotations:json
标记的每种颜色表示一个span(有七个span — 从A到G),请考虑如下标记:bootstrap
Trace Id = X Span Id = D Client Sent
此标记表示当前span的Trace Id设置为X,Span Id设置为D,此外,还发生了Client Sent
事件。服务器
下图显示了span的父—子关系:网络
如下部分参考上图中显示的示例。
这个例子有七个span,若是你进入Zipkin中的trace,你能够在第二个trace中看到这个数字,以下图所示:
可是,若是选择特定trace,则能够看到四个span,以下图所示:
选择特定trace时,你会看到合并的span,这意味着,若是经过Server Received和Server Sent或Client Received和Client Sent annotations向Zipkin发送了两个span,则它们将显示为单个span。
在这种状况下,为何七个和四个span之间存在差别?
http:/start
span,它具备Server Received(sr
)和Server Sent(ss
)annotations。service1
到service2
的http:/foo
端点的RPC调用,Client Sent(cs
)和Client Received(cr
)事件发生在service1
端,Server Received(sr
)和Server Sent(ss
)事件发生在service2
端,这两个span造成一个与RPC调用相关的逻辑span。service2
到service3
的http:/bar
端点的RPC调用,Client Sent(cs
)和Client Received(cr
)事件发生在service2
端,Server Received(sr
)和Server Sent(ss
)事件发生在service3
端,这两个span造成一个与RPC调用相关的逻辑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显示错误的缘由以及与之相关的整个堆栈追踪。
从版本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}" } } }
一般,你不但愿将日志存储在文本文件中,而是存储在Logstash能够当即选择的JSON文件中,为此,你必须执行如下操做(为了便于阅读,在groupId:artifactId:version
notation中传递依赖项)。
依赖关系设置
ch.qos.logback:logback-core
)。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配置文件:
build/${spring.application.name}.json
文件中。若是使用自定义logback-spring.xml
,则必须在bootstrap
而不是application
属性文件中传递spring.application.name
,不然,你的自定义logback文件没法正确读取该属性。
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随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"));
本节介绍如何使用Maven或Gradle将Sleuth添加到项目中。
要确保你的应用程序名称在Zipkin中正确显示,请在bootstrap.yml
中设置spring.application.name
属性。
若是你想在没有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-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-cloud-starter-sleuth
。若是你想要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而不是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-cloud-starter-zipkin
,这样,全部嵌套的依赖项都会被下载。spring-rabbit
依赖项。Gradle
dependencyManagement { 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 }
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的不一样设置。