文章主要翻譯自Spring Cloud Sleuth官方文檔
Spring-Cloud
Spring Cloud為開發(fā)者提供了在分布式系統(tǒng)(如配置管理嘲碱、服務(wù)發(fā)現(xiàn)、斷路器、智能路由云头、微代理、控制總線量愧、一次性Token钾菊、全局鎖、決策競(jìng)選偎肃、分布式會(huì)話和集群狀態(tài))操作的開發(fā)工具煞烫。使用SpringCloud開發(fā)者可以快速實(shí)現(xiàn)上述這些模式。
Spring Cloud Sleuth
Distributed tracing for Spring Cloud applications, compatiblewith Zipkin, HTrace and log-based(e.g. ELK)tracing.
Spring-Cloud-Sleuth是Spring Cloud的組成部分之一累颂,為SpringCloud應(yīng)用實(shí)現(xiàn)了一種分布式追蹤解決方案滞详,其兼容了Zipkin, HTrace和log-based追蹤
術(shù)語(Terminology)
Span:基本工作單元,例如紊馏,在一個(gè)新建的span中發(fā)送一個(gè)RPC等同于發(fā)送一個(gè)回應(yīng)請(qǐng)求給RPC料饥,span通過一個(gè)64位ID唯一標(biāo)識(shí),trace以另一個(gè)64位ID表示朱监,span還有其他數(shù)據(jù)信息岸啡,比如摘要、時(shí)間戳事件赫编、關(guān)鍵值注釋(tags)巡蘸、span的ID、以及進(jìn)度ID(通常是IP地址)
span在不斷的啟動(dòng)和停止擂送,同時(shí)記錄了時(shí)間信息悦荒,當(dāng)你創(chuàng)建了一個(gè)span,你必須在未來的某個(gè)時(shí)刻停止它嘹吨。
Trace:一系列spans組成的一個(gè)樹狀結(jié)構(gòu)搬味,例如,如果你正在跑一個(gè)分布式大數(shù)據(jù)工程躺苦,你可能需要?jiǎng)?chuàng)建一個(gè)trace身腻。
Annotation:用來及時(shí)記錄一個(gè)事件的存在,一些核心annotations用來定義一個(gè)請(qǐng)求的開始和結(jié)束
- cs- Client Sent -客戶端發(fā)起一個(gè)請(qǐng)求匹厘,這個(gè)annotion描述了這個(gè)span的開始
- sr- Server Received -服務(wù)端獲得請(qǐng)求并準(zhǔn)備開始處理它嘀趟,如果將其sr減去cs時(shí)間戳便可得到網(wǎng)絡(luò)延遲
- ss- Server Sent -注解表明請(qǐng)求處理的完成(當(dāng)請(qǐng)求返回客戶端),如果ss減去sr時(shí)間戳便可得到服務(wù)端需要的處理請(qǐng)求時(shí)間
- cr- Client Received -表明span的結(jié)束愈诚,客戶端成功接收到服務(wù)端的回復(fù)她按,如果cr減去cs時(shí)間戳便可得到客戶端從服務(wù)端獲取回復(fù)的所有所需時(shí)間
將Span和Trace在一個(gè)系統(tǒng)中使用Zipkin注解的過程圖形化:

每個(gè)顏色的注解表明一個(gè)span(總計(jì)7個(gè)spans牛隅,從A到G),如果在注解中有這樣的信息:
Trace Id = X
Span Id = D
Client Sent
這就表明當(dāng)前span將Trace-Id設(shè)置為X酌泰,將Span-Id設(shè)置為D媒佣,同時(shí)它還表明了ClientSent事件。
spans的parent/child關(guān)系圖形化:
目的(Purpose)
基于Zipkin的分布式追蹤
總計(jì)11個(gè)spans陵刹,如果在Zipkin中查看traces將看到如下圖:

但如果你選取一個(gè)特殊的trace你將看到8個(gè)spans:

當(dāng)選取一個(gè)特殊trace時(shí)你會(huì)看到合并的spans默伍,這意味著如果有兩個(gè)spans使用客戶端接收發(fā)送/服務(wù)端接收發(fā)送注解發(fā)送至Zipkin時(shí),他們將表現(xiàn)為一個(gè)單獨(dú)的span
在展示Span和Trace圖形化的圖片中有20個(gè)顏色標(biāo)簽衰琐,Zipkin又是如何接收10個(gè)spans的呢也糊?
- 2個(gè)span A標(biāo)簽表明span的開始和結(jié)束,接近結(jié)束時(shí)一個(gè)單獨(dú)的span發(fā)送給Zipkin
- 4個(gè)span B標(biāo)簽實(shí)際上是一個(gè)有4個(gè)注解的單獨(dú)span羡宙,然而這個(gè)span是由兩個(gè)分離的實(shí)例組成的狸剃,一個(gè)由service 1發(fā)出,一個(gè)由service 2發(fā)出狗热,因此實(shí)際上兩個(gè)span實(shí)例是發(fā)送到Zipkin并在那合并
- 2個(gè)span C標(biāo)簽表明span的開始和結(jié)束钞馁,接近結(jié)束時(shí)一個(gè)單獨(dú)的span發(fā)送給Zipkin
- 4個(gè)span D標(biāo)簽實(shí)際上是一個(gè)有4個(gè)注解的單獨(dú)span,然而這個(gè)span是由兩個(gè)分離的實(shí)例組成的匿刮,一個(gè)由service 2發(fā)出僧凰,一個(gè)由service 3發(fā)出,因此實(shí)際上兩個(gè)span實(shí)例是發(fā)送到Zipkin并在那合并
- 2個(gè)span E標(biāo)簽表明span的開始和結(jié)束熟丸,接近結(jié)束時(shí)一個(gè)單獨(dú)的span發(fā)送給Zipkin
- 4個(gè)span F標(biāo)簽實(shí)際上是一個(gè)有4個(gè)注解的單獨(dú)span允悦,然而這個(gè)span是由兩個(gè)分離的實(shí)例組成的,一個(gè)由service 2發(fā)出虑啤,一個(gè)由service 4發(fā)出隙弛,因此實(shí)際上兩個(gè)span實(shí)例是發(fā)送到Zipkin并在那合并
- 2個(gè)span G標(biāo)簽表明span的開始和結(jié)束,接近結(jié)束時(shí)一個(gè)單獨(dú)的span發(fā)送給Zipkin
因此1個(gè)span來自A狞山,2個(gè)span來自B全闷,1個(gè)span來自C,2個(gè)span來自D萍启,1個(gè)span來自E总珠,2個(gè)span來自F,1個(gè)來自G勘纯,總計(jì)10個(gè)spans局服。
Zipkin中的依賴圖:

Log相關(guān)
當(dāng)使用trace id為2485ec27856c56f4抓取這四個(gè)應(yīng)用的log時(shí),會(huì)獲得如下輸出:
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等淫奔,你可以看到事件的發(fā)生信息,Kibana的例子如下:

以下是Logstash的Grok模式:
filter {
# pattern matching logback pattern
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+
\s+%{DATA:pid}---\s+
\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
}
}
JSON Logback with Logstash
為了方便獲取Logstash堤结,通常保存log在JSON文件中而不是text文件中唆迁,配置方法如下:
依賴建立
確保Logback在classpath中(ch.qos.logback:logback-core)
增加LogstashLogback編碼- version 4.6的例子:net.logstash.logback:logstash-logback-encoder:4.6
Logback建立
以下是一個(gè)Logback配置的例子:
- 使用JSON格式記錄應(yīng)用信息到build/${spring.application.name}.json文件
- 有兩個(gè)添加注釋源- console和標(biāo)準(zhǔn)log文件
- 與之前章節(jié)使用相同的log模式
<?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}"/>
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow} %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>INFO</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:-}",
"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"/>-->
<appender-ref ref="logstash"/>
<!--<appender-ref ref="flatfile"/>-->
</root>
</configuration>
添加進(jìn)工程
僅Sleuth(log收集)
如果僅需要Spring Cloud Sleuth而不需要Zipkin集成鸭丛,只需要增加spring-cloud-starter-sleuth模塊到你工程中
- 為了不手動(dòng)添加版本號(hào),更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-starter-sleuth
<dependencyManagement> (1)
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency> (2)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
- 為了不手動(dòng)添加版本號(hào)唐责,更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-starter-sleuth
dependencyManagement { (1)
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
}
}
dependencies { (2)
compile "org.springframework.cloud:spring-cloud-starter-sleuth"
}
通過HTTP使用基于Zipkin的Sleuth
如果你需要Sleuth和Zipkin鳞溉,只需要添加spring-cloud-starter-zipkin依賴
- 為了不手動(dòng)添加版本號(hào),更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-starter-zipkin
<dependencyManagement> (1)
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency> (2)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
- 為了不手動(dòng)添加版本號(hào)鼠哥,更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-starter-zipkin
dependencyManagement { (1)
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
}
}
dependencies { (2)
compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}
通過Spring Cloud Stream使用Sleuth+Zipkin
- 為了不手動(dòng)添加版本號(hào)熟菲,更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-sleuth-stream
- 添加依賴到spring-cloud-starter-sleuth
- 添加一個(gè)binder(e.g.Rabbit binder)來告訴Spring Cloud Stream應(yīng)該綁定什么
<dependencyManagement> (1)
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency> (2)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-stream</artifactId>
</dependency>
<dependency> (3)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- EXAMPLE FOR RABBIT BINDING -->
<dependency> (4)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
- 為了不手動(dòng)添加版本號(hào),更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-sleuth-stream
- 添加依賴到spring-cloud-starter-sleuth
- 添加一個(gè)binder(e.g.Rabbit binder)來告訴Spring Cloud Stream應(yīng)該綁定什么
dependencyManagement { (1)
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-sleuth-stream" (2)
compile "org.springframework.cloud:spring-cloud-starter-sleuth" (3)
// Example for Rabbit binding
compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" (4)
}
Spring Cloud Sleuth Stream Zipkin Collector
啟動(dòng)一個(gè)Spring Cloud Sleuth Stream Zipkin收集器只需要添加spring-cloud-sleuth-zipkin-stream依賴
- 為了不手動(dòng)添加版本號(hào)朴恳,更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-sleuth-zipkin-stream
- 添加依賴到spring-cloud-starter-sleuth
- 添加一個(gè)binder(e.g.Rabbit binder)來告訴Spring Cloud Stream應(yīng)該綁定什么
<dependencyManagement> (1)
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency> (2)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency> (3)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!-- EXAMPLE FOR RABBIT BINDING -->
<dependency> (4)
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
spring-cloud-stream-binder-rabbit
- 為了不手動(dòng)添加版本號(hào)科盛,更好的方式是通過Spring BOM添加dependencymanagement
- 添加依賴到spring-cloud-sleuth-zipkin-stream
- 添加依賴到spring-cloud-starter-sleuth
- 添加一個(gè)binder(e.g.Rabbit binder)來告訴Spring Cloud Stream應(yīng)該綁定什么
dependencyManagement { (1)
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
}
}
dependencies {
compile "org.springframework.cloud:spring-cloud-sleuth-zipkin-stream" (2)
compile "org.springframework.cloud:spring-cloud-starter-sleuth" (3)
// Example for Rabbit binding
compile "org.springframework.cloud:spring-cloud-stream-binder-rabbit" (4)
}
之后只需要在你的主類中添加@EnableZipkinStreamServer注解
package example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
@SpringBootApplication
@EnableZipkinStreamServer
public class ZipkinStreamServerApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZipkinStreamServerApplication.class, args);
}
}
特點(diǎn)(Features)
添加trace和spanid到Slf4J MDC,然后就可以從一個(gè)給定的trace或span中提取所有的log菜皂,例如
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中的[appname,traceId,spanId,exportable]:
- spanId- the id of a specific operation that took place
- appname- the name of the application that logged the span
- traceId- the id of the latency graph that contains the span
- exportable- whether the log should be exported to Zipkin or not. Whenwould you like the span not to be exportable? In the case in which you want towrap some operation in a Span and have it written to the logs only.
在通常的分布式追蹤數(shù)據(jù)模型上提供一種抽象模型:traces、spans(生成一個(gè)DAG)厉萝、annotations恍飘、key-value annotations∏吹妫基于HTrace是較為寬松的章母,但Zipkin(Dapper)更具兼容性
Sleuth記錄時(shí)間信息來幫助延遲分析,使用Sleuth可以精確找到應(yīng)用中延遲的原因翩剪,Sleuth不會(huì)log太多乳怎,因此不會(huì)導(dǎo)致你的應(yīng)用掛掉
- propagatesstructural data about your call-graph in-band, and the rest out-of-band
- includesopinionated instrumentation of layers such as HTTP
- includessampling policy to manage volume
- canreport to a Zipkin system for query and visualization
使用Spring應(yīng)用裝備出入口點(diǎn)(servletfilter、async endpoints前弯、rest template蚪缀、scheduled actions、messagechannels恕出、zuul filters询枚、feign client)
Sleuth包含默認(rèn)邏輯通過http或messaging boundaries來加入一個(gè)trace,例如浙巫,http傳播通過Zipkin-compatiblerequest headers工作,這個(gè)傳播邏輯定義和定制是通過SpanInjector和SpanExtractor實(shí)現(xiàn)提供簡(jiǎn)單的接受或放棄span
度量(metrics)
如果依賴了spring-cloud-sleuth-zipkin,應(yīng)用將生成并收集Zipkin-compatible traces弓乙,一般會(huì)通過HTTP將這些traces發(fā)送給一個(gè)本地Zipkin服務(wù)器(port 9411)足淆,使用spring.zipkin.baseUrl來配置服務(wù)的地址
如果依賴了spring-cloud-sleuth-stream,應(yīng)用將通過Spring Cloud Stream生成并收集traces丧裁,應(yīng)用自動(dòng)成為tracer消息的生產(chǎn)者护桦,這些消息會(huì)通過你的中間件分發(fā)(e.g. RabbitMQ,Apache Kafka,Redis)
如果使用Zipkin或Stream,使用spring.sleuth.sampler.percentage配置輸出spans的百分比(默認(rèn)10%)煎娇,不然你可能會(huì)認(rèn)為Sleuth沒有工作嘶炭,因?yàn)樗÷粤艘恍﹕pans
SLF4J MDC一直處于工作狀態(tài)抱慌,logback用戶可以在logs中立刻看到trace和span id,其他logging系統(tǒng)不得不配置他們自己的模式以得到相同的結(jié)果眨猎,默認(rèn)logging.pattern.level設(shè)置為%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow}(對(duì)于logback用戶抑进,這是一種Spring Boot特征),這意味著如果你沒有使用SLF4J這個(gè)模式將不會(huì)自動(dòng)適用
抽樣(Samling)
在分布式追蹤時(shí)睡陪,數(shù)據(jù)量可能會(huì)非常大寺渗,因此抽樣就變得非常重要(通常不需要導(dǎo)出所有的spans以得到事件發(fā)生原貌),Spring Cloud Sleuth有一個(gè)Sampler戰(zhàn)略兰迫,即用戶可以控制抽樣算法信殊,Samplers不會(huì)停止正在生成的span id(相關(guān)的),但他們會(huì)阻止tags和events附加和輸出汁果,默認(rèn)戰(zhàn)略是當(dāng)一個(gè)span處于活躍狀態(tài)會(huì)繼續(xù)trace涡拘,但新的span會(huì)一直處于不輸出狀態(tài),如果所有應(yīng)用都使用這個(gè)sampler据德,你會(huì)在logs中看到traces鳄乏,但不會(huì)出現(xiàn)在任何遠(yuǎn)程倉庫。測(cè)試狀態(tài)資源都是充足的棘利,并且你只使用logs的話他就是你需要的全部(e.g.一個(gè)ELK集合)橱野,如果輸出span數(shù)據(jù)到Zipkin或Spring Cloud Stream,有AlwaysSampler輸出所有數(shù)據(jù)和PercentageBasedSampler采樣spans確定的一部分善玫。
如果使用spring-cloud-sleuth-zipkin或spring-cloud-sleuth-stream水援,PercentageBasedSampler是默認(rèn)的,你可以使用spring.sleuth.sampler.percentage配置輸出
通過創(chuàng)建一個(gè)bean定義就可以新建一個(gè)sampler
@Bean
public Sampler defaultSampler() {
return new AlwaysSampler();
}
Instrumentation
Spring Cloud Sleuth自動(dòng)裝配所有Spring應(yīng)用茅郎,因此你不用做任何事來讓他工作蜗元,裝配是使用一系列技術(shù)添加的,例如對(duì)于一個(gè)servlet web應(yīng)用我們使用一個(gè)Filter系冗,對(duì)于SpringIntegration我們使用ChannelInterceptors许帐。
用戶可以使用span tags定制關(guān)鍵字,為了限制span數(shù)據(jù)量毕谴,一般一個(gè)HTTP請(qǐng)求只會(huì)被少數(shù)元數(shù)據(jù)標(biāo)記成畦,例如status code、host以及URL涝开,用戶可以通過配置spring.sleuth.keys.http.headers(一系列頭名稱)添加request headers循帐。
tags僅在Sampler允許其被收集和輸出時(shí)工作(默認(rèn)情況其不工作,因此不會(huì)有在不配置的情況下收集過多數(shù)據(jù)的意外危險(xiǎn)出現(xiàn))
Span生命周期
通過Trace接口的方式可以在Span上進(jìn)行如下操作:
- start-當(dāng)打開一個(gè)span時(shí)舀武,其名字被指定且開始時(shí)間戳被記錄
- close- span已經(jīng)結(jié)束(span的結(jié)束時(shí)間已被記錄)并且如果span是輸出的拄养,他將是Zipkin合適的收集項(xiàng),span在當(dāng)前線程也將被移除
- continue- span的一個(gè)新實(shí)例將被創(chuàng)建,然而他將是正是正在運(yùn)行的span的一個(gè)復(fù)制體
- detach- span不會(huì)停止或關(guān)閉瘪匿,他只會(huì)被從當(dāng)前線程中移除
- create with explicit parent-建立一個(gè)新的span并設(shè)置一個(gè)明確的parent給他
新建和關(guān)閉spans
使用Tracer接口可以手動(dòng)新建spans
// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.createSpan("calculateTax");
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
newSpan.logEvent("taxCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin
this.tracer.close(newSpan);
}
在例子中我們可以看到如何新建一個(gè)span實(shí)例跛梗,假設(shè)在當(dāng)前線程中已經(jīng)有一個(gè)span,那么新建的線程將會(huì)是這個(gè)線程的parent棋弥。
新建span后要記得清除他核偿!如果你想要將一個(gè)span發(fā)送給Zipkin,不要忘記關(guān)閉他顽染。
持續(xù)(Continuing)spans
有時(shí)你不想要新建一個(gè)span但你又想持續(xù)使用漾岳,這種情況的例子可能如下(當(dāng)然實(shí)際依賴于使用情況):
- AOP-如果在實(shí)際應(yīng)用前已經(jīng)有一個(gè)span新建可用,那么就不需要新建一個(gè)span
- Hystrix-對(duì)于當(dāng)前處理流程而言粉寞,執(zhí)行Hystrix操作是最為合理的一部分尼荆,實(shí)際上只有技術(shù)實(shí)現(xiàn)細(xì)節(jié)的話,不必將他作為分離的部分反映在tracing中
span的持續(xù)實(shí)例等同于正在運(yùn)行的:
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);
可以使用Tracer接口延續(xù)一個(gè)span
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
continuedSpan.logEvent("taxCalculated");
} finally {
// Once done remember to detach the span. That way you'll
// safely remove it from the current thread without closing it
this.tracer.detach(continuedSpan);
}
新建一個(gè)span后記得清除他唧垦!如果有些工作在一個(gè)線程(e.g. thread X)中已經(jīng)結(jié)束并且他在等待另外的線程(e.g. Y,Z)結(jié)束時(shí)捅儒,不要忘記分離span,在線程Y,Z中的spans在他們工作結(jié)束時(shí)也應(yīng)被分離振亮,結(jié)果收集完成時(shí)thread X中的span應(yīng)該被關(guān)閉
使用明確的parent新建spans
如果你想新建一個(gè)span并且提供一個(gè)明確的parent給他巧还,假設(shè)span的parent在一個(gè)thread中,而你想在另一個(gè)thread中新建span双炕,Tracer接口的startSpan命令就是你需要的。
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("commissionValue", commissionValue);
// ...
// You can log an event on a span
newSpan.logEvent("commissionCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin. The tags and events set on the
// newSpan will not be present on the parent
this.tracer.close(newSpan);
}
記得在新建這樣的span后關(guān)閉他撮抓,否則你在你的log中看到大量的相關(guān)warning妇斤,更糟糕的是你的span不會(huì)正常關(guān)閉,這樣的話就無法被Zipkin收集
命名spans
為span命名是很重要的工作丹拯,span名稱必須描述了一個(gè)操作名稱站超,名稱必須要簡(jiǎn)明(e.g.不包括標(biāo)識(shí)符)。
Since there is a lot of instrumentation going on some of thespan names will be artificial like:
- controller-method-namewhen received by a Controller with a methodnameconrollerMethodName
- asyncfor asynchronous operations done via wrappedCallableandRunnable
- @Scheduledannotated methods will return the simple nameof the class
Fortunately, for the asynchronous processing you can provideexplicit naming.
@SpanName注解
可以使用@SpanName注解明確命名span
@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {
@Override public void run() {
// perform logic
}
}
在這種情況下乖酬,使用下面的方式便命名一個(gè)span為calculateTax
Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable());
Future future = executorService.submit(runnable);
// ... some additional logic ...
future.get();
toString()方法
為Runnable或Callable建立分離的classes是非常少見的死相,一般建立這些classes的匿名實(shí)例,你不能注解這些classes除非override咬像,如果沒有@SpanName注解算撮,我們將會(huì)檢查class是否使用傳統(tǒng)的toString()方法實(shí)現(xiàn)
執(zhí)行這些代碼將新建一個(gè)名為calculateTax的span:
Runnable runnable = new TraceRunnable(tracer, spanNamer, new Runnable() {
@Override public void run() {
// perform logic
}
@Override public String toString() {
return "calculateTax";
}
});
Future future = executorService.submit(runnable);
// ... some additional logic ...
future.get();
定制化
使用SpanInjector和SpanExtractor你可以定制化span的新建和傳播。
當(dāng)前有兩種built-in方法來在進(jìn)程間傳遞tracing信息:
- 通過SpringIntegration
- 通過HTTP
span id是從Zipkin-compatible(B3)頭中提取的(不論Message或HTTP頭)县昂,以此來開始或加入一個(gè)存在的trace肮柜,trace信息被注入到輸出請(qǐng)求中,這樣后面的步驟就可以提取他倒彰。
Spring Integration
對(duì)于Spring Integration审洞,存在beans負(fù)責(zé)span從Message的創(chuàng)建和使用tracing信息裝配MessageBuilder。
@Bean
public SpanExtractor messagingSpanExtractor() {
...
}
@Bean
public SpanInjector messagingSpanInjector() {
...
}
用戶可以使用自己的實(shí)現(xiàn)來override他待讳,或者添加@Primary注解到你的bean定義
HTTP
對(duì)于HTTP芒澜,存在beans負(fù)責(zé)span從HttpServletRequest的創(chuàng)建和使用tracing信息裝配HttpServletResponse仰剿。
@Bean
public SpanExtractor httpServletRequestSpanExtractor() {
...
}
@Bean
public SpanInjector httpServletResponseSpanInjector() {
...
}
用戶可以使用自己的實(shí)現(xiàn)來override他,或者添加@Primary注解到你的bean定義
例子
對(duì)比傳統(tǒng)的兼容Zipkin痴晦,tracingHTTP頭名有以下格式
- traceid - correlationId
- spanid - mySpanId
以下是一個(gè)SpanExtractor的例子
static class CustomHttpServletRequestSpanExtractor
implements SpanExtractor {
@Override
public Span joinTrace(HttpServletRequest carrier) {
long traceId = Span.hexToId(carrier.getHeader("correlationId"));
long spanId = Span.hexToId(carrier.getHeader("mySpanId"));
// extract all necessary headers
Span.SpanBuilder builder = Span.builder().traceId(traceId).spanId(spanId);
// build rest of the Span
return builder.build();
}
}
以下SpanInjector將被建立
static class CustomHttpServletResponseSpanInjector
implements SpanInjector {
@Override
public void inject(Span span, HttpServletResponse carrier) {
carrier.addHeader("correlationId", Span.idToHex(span.getTraceId()));
carrier.addHeader("mySpanId", Span.idToHex(span.getSpanId()));
// inject the rest of Span values to the header
}
}
并且你可以這樣注冊(cè)他們
@Bean
@Primary
SpanExtractor customHttpServletRequestSpanExtractor() {
return new CustomHttpServletRequestSpanExtractor();
}
@Bean
@Primary
SpanInjector customHttpServletResponseSpanInjector() {
return new CustomHttpServletResponseSpanInjector();
}
SpringData as Messages
可以通過Spring Cloud Stream來積累和發(fā)送span數(shù)據(jù)南吮,配置時(shí)需要包含spring-cloud-sleuth-streamjar為依賴且增加一個(gè)Channel Binder實(shí)現(xiàn)方式(e.g. spring-cloud-starter-stream-rabbit對(duì)應(yīng)RabbitMQ或spring-cloud-starter-stream-kafka對(duì)應(yīng)Kafka),使用payload格式Spans將自動(dòng)把你的app變?yōu)橐粋€(gè)信息生產(chǎn)者
Zipkin Consumer
有一種特殊而又便利的注解方式阅酪,即為span數(shù)據(jù)建立一個(gè)信息消費(fèi)者旨袒,并將他推到一個(gè)Zipkin SpanStrore中
@SpringBootApplication
@EnableZipkinStreamServer
public class Consumer {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
這種應(yīng)用將通過Spring Cloud Stream Binder監(jiān)聽不論何種方式傳輸?shù)膕pan數(shù)據(jù)(e.g.包括spring-cloud-starter-stream-rabbit對(duì)應(yīng)RabbitMQ,和對(duì)應(yīng)Redis和Kafka的類似starter存在)术辐,如果添加以下UI依賴
io.zipkin.[Java](http://lib.csdn.net/base/17)
zipkin-autoconfigure-ui
你將啟動(dòng)一個(gè)Zipkin server應(yīng)用砚尽,他將通過端口9411訪問UI和api。
默認(rèn)SpanStore是in-memory的(適合于demos且啟動(dòng)迅速)辉词,你可以添加MySQL和spring-boot-starter-jdbc到你的系統(tǒng)環(huán)境并通過配置激活JDBC SpanStore必孤。例如:
spring:
rabbitmq:
host: ${RABBIT_HOST:localhost}
datasource:
schema: classpath:/mysql.sql
url: jdbc:mysql://${MYSQL_HOST:localhost}/test
username: root
password: root
# Switch this on to create the schema on startup:
initialize: true
continueOnError: true
sleuth:
enabled: false
zipkin:
storage:
type: mysql
@EnableZipkinStreamServer也使用@EnableZipkinServer注解,因此進(jìn)程也會(huì)顯示標(biāo)準(zhǔn)Zipkin服務(wù)終端以通過HTTP收集span瑞躺,且可以通過Zipkin Web UI查詢
定制消費(fèi)者
使用spring-cloud-sleuth-stream且綁定SleuthSink可以很方便的實(shí)現(xiàn)定制消費(fèi)者敷搪。例子:
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class Consumer {
@ServiceActivator(inputChannel = SleuthSink.INPUT)
public void sink(Spans input) throws Exception {
// ... process spans
}
}
上述的消費(fèi)者應(yīng)用明確排除SleuthStreamAutoConfiguration,因此他不會(huì)給自己發(fā)消息幢哨,但這是可選的(你可能想要trace請(qǐng)求到消費(fèi)者app)
度量(Metrics)
當(dāng)前Spring Cloud Sleuth記錄非常簡(jiǎn)單的spans metrics赡勘,使用Spring Boot的metrics support來計(jì)算接收丟棄的span數(shù)量,當(dāng)有span發(fā)送給Zipkin時(shí)捞镰,接收span的數(shù)量就會(huì)增加闸与,如果有錯(cuò)誤發(fā)生,丟棄span數(shù)量就會(huì)增加岸售。
Integrations
Runable和Callable
如果你要將你的邏輯包裹在Runable或Callable中践樱,足夠?qū)⑦@些classes放到他們的Sleuth代表中。
Runnable的例子:
Runnable runnable = new Runnable() {
@Override
public void run() {
// do some work
}
@Override
public String toString() {
return "spanNameFromToStringMethod";
}
};
// Manual `TraceRunnable` creation with explicit "calculateTax" Span name
Runnable traceRunnable = new TraceRunnable(tracer, spanNamer, runnable, "calculateTax");
// Wrapping `Runnable` with `Tracer`. The Span name will be taken either from the
// `@SpanName` annotation or from `toString` method
Runnable traceRunnableFromTracer = tracer.wrap(runnable);
Callable的例子:
Callable callable = new Callable() {
@Override
public String call() throws Exception {
return someLogic();
}
@Override
public String toString() {
return "spanNameFromToStringMethod";
}
};
// Manual `TraceCallable` creation with explicit "calculateTax" Span name
Callable traceCallable = new TraceCallable<>(tracer, spanNamer, callable, "calculateTax");
// Wrapping `Callable` with `Tracer`. The Span name will be taken either from the
// `@SpanName` annotation or from `toString` method
Callable traceCallableFromTracer = tracer.wrap(callable);
這種方式你可以保證一個(gè)新的Span在每次執(zhí)行時(shí)新建和關(guān)閉凸丸。
Hystrix
傳統(tǒng)并發(fā)策略
我們以將所有的Callable實(shí)例置入到他們的Sleuth代表-TraceCallable的方式來記錄一個(gè)傳統(tǒng)的HystrixConcurrencyStrategy拷邢,策略的打開或延續(xù)一個(gè)span取決于在Hystrix操作被調(diào)用前tracing是否在工作,為了使傳統(tǒng)Hystrix并發(fā)策略無效可以設(shè)置spring.sleuth.hystrix.strategy.enable為false屎慢。
手動(dòng)操作設(shè)置
假設(shè)你有以下HystrixCommand:
HystrixCommand hystrixCommand = new HystrixCommand(setter) {
@Override
protected String run() throws Exception {
return someLogic();
}
};
為了傳遞tracing信息你必須將同樣的邏輯置于HystrixCommand的Sleuth版本中瞭稼,也就是TraceCommand:
TraceCommand traceCommand = new TraceCommand(tracer, traceKeys, setter) {
@Override
public String doRun() throws Exception {
return someLogic();
}
};
RxJava
我們記錄了一個(gè)典型的RxJavaSchedulersHook,他將所有Action0實(shí)例置入到他們的Sleuth代表-TraceAction中腻惠,hook打開或延續(xù)一個(gè)span取決于Action被安排前tracing是否已經(jīng)在工作弛姜,為了使RxJavaSchedulersHook無效可設(shè)置spring.sleuth.rxjava.schedulers.hook.enabled為false。
You can define a list of regular expressions for thread names,for which you don’twant a Span to be created. Just provide a comma separated list of regularexpressions in thespring.sleuth.rxjava.schedulers.ignoredthreadsproperty.
HTTP integration
將spring.sleuth.web.enabled配置值設(shè)置為false可以使這章中的特征方法無效
HTTP Filter
通過TraceFilter妖枚,所有抽樣輸入的請(qǐng)求都會(huì)歸結(jié)到span的創(chuàng)建廷臼,span的名稱為"http+請(qǐng)求發(fā)送的路徑",例如,如果請(qǐng)求發(fā)送到/foo/bar荠商,名稱即為http:/foo/bar寂恬,你可以配置通過spring.sleuth.web.skipPattern,那些URIs將被過濾掉莱没,如果你在環(huán)境中添加了ManagementServerProperties初肉,你的contextPath值會(huì)附加到過濾配置上。
HandlerIntercepter
由于需要span名稱的精確饰躲,我們使用一個(gè)TraceHandlerInterceptor來置入一個(gè)存在的HandlerInterceptor或直接添加到存在的HandlerInterceptors列表中牙咏,TraceHandlerInterceptor添加一個(gè)特殊的請(qǐng)求屬性給HttpServletRequest,如果TraceFilter沒有看到屬性嘹裂,他會(huì)建立一個(gè)"fallback"span妄壶,這是一個(gè)建立在服務(wù)端的附加的span,此時(shí)trace在UI中可以正確的顯示寄狼。
HTTP client integration
同步RestTemplate
我們注入一個(gè)RestTemplate攔截器來保證所有的tracing信息被發(fā)送到請(qǐng)求端丁寄,每當(dāng)一個(gè)請(qǐng)求被生成,一個(gè)新的span將被創(chuàng)建泊愧,他會(huì)在接收應(yīng)答后關(guān)閉伊磺,為了限制同步RestTemplate只需要設(shè)置spring.sleuth.web.client.enabled為false。
你必須注冊(cè)一個(gè)RestTemplate為bean以使得攔截器可以注入删咱,如果你使用一個(gè)新的關(guān)鍵字建立一個(gè)RestTemplate實(shí)例屑埋,instrumentation將無法工作
異步RestTemplate
傳統(tǒng)的instrumentation是通過發(fā)送接收請(qǐng)求來建立關(guān)閉span的,你可以通過注冊(cè)你的bean來定制ClientHttpRequestFactory和AsyncClientHttpRequestFactory痰滋,記得使用tracing compatible實(shí)現(xiàn)方式(e.g.不要忘記將ThreadPoolTaskScheduler置入一個(gè)TraceAsyncListenableTaskExecutor)摘能,傳統(tǒng)請(qǐng)求工廠例子如下:
Unresolved directive in spring-cloud-sleuth.adoc - include::../../../../spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebAsyncClientAutoConfigurationTest.java[tags=async_template_factories,indent=0]
通過設(shè)置spring.sleuth.web.async.client.enabled為false可以限制AsyncRestTemplate,使默認(rèn)的TraceAsyncClientHttpRequestFactoryWrapper無效可以設(shè)置spring.sleuth.web.async.client.factory.enabled為false即寡,如果你不想創(chuàng)建AsyncRestClient徊哑,設(shè)置spring.sleuth.web.async.client.template.enabled為false袜刷。
Feign
默認(rèn)Spring Cloud Sleuth通過TraceFeignClientAutoConfiguration提供feign的集成聪富,你可以設(shè)置spring.sleuth.feign.enabled為false來使他無效,如果這樣設(shè)置那么所有feign相關(guān)的裝配都無法發(fā)生著蟹。
通過FeignBeanPostProcessor feign裝配的部分結(jié)束墩蔓,可以設(shè)置spring.sleuth.feign.processor.enabled為false來是他無效化,如果你這樣設(shè)置萧豆,Spring Cloud Sleuth將不會(huì)裝配任何你的傳統(tǒng)feign組件奸披,所有默認(rèn)裝配保持原有狀態(tài)。
異步通信
@Async注解方法
在Spring Cloud Sleuth中涮雷,我們裝配異步關(guān)聯(lián)組件以使得tracing信息可以在threads間傳遞阵面,你可以通過設(shè)置spring.sleuth.async.enabled值為false來使其無效化。
如果你使用@Async來注解你的方法,我們將自動(dòng)建立一個(gè)新的span:
- span名稱將是注解方法名
- span將被標(biāo)注為方法類名和方法名
@Scheduled注解方法
在Spring Cloud Sleuth中样刷,我們裝配scheduled執(zhí)行方法以使得tracing信息可以在threads間傳遞仑扑,你可以通過設(shè)置spring.sleuth.scheduled.enabled值為false來使其無效化。
如果你使用@Scheduled來注解你的方法置鼻,我們將自建立一個(gè)新的span:
span名稱將是注解方法名
span將被標(biāo)注為方法類名和方法名
如果在一些@Scheduled注解類中你想跳過span新建過程镇饮,可以設(shè)置spring.sleuth.scheduled.skipPattern為一個(gè)指定的表達(dá)式,這將匹配@Scheduled注解類的完整描述名稱箕母。
Executor, ExecutorServiceand ScheduledExecutorService
我們提供了LazyTraceExecutor储藐,TraceableExecutorService和TraceableScheduledExecutorService。每當(dāng)一個(gè)新的任務(wù)被提交嘶是、調(diào)用或scheduled時(shí)钙勃,這些實(shí)現(xiàn)會(huì)建立新的spans。
以下是當(dāng)使用CompletableFuture時(shí)如何用TraceableExecutorService傳遞tracing信息:
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
// perform some logic
return 1_000_000L;
}, new TraceableExecutorService(executorService,
// 'calculateTax' explicitly names the span - this param is optional
tracer, traceKeys, spanNamer, "calculateTax"));
消息傳遞
Spring Cloud Sleuth集成了Spring Integration俊啼。他會(huì)建立span來發(fā)布或訂閱事件肺缕,設(shè)置spring.sleuth.integration.enabled為false可以使Spring Integration無效。
Spring Cloud Sleuth到1.0.4版本前都是使用消息傳遞時(shí)發(fā)送無效tracing頭授帕,這些頭和在HTTP(包含- )發(fā)送的名稱時(shí)一樣的同木,為了在1.0.4版本的向后兼容目的,我們開始發(fā)送所有有效和無效的頭跛十,請(qǐng)更新到1.0.4彤路,因?yàn)樵赟pring Cloud Sleuth 1.1中我們將會(huì)移除對(duì)分離頭的支持。
從1.0.4后可以明確設(shè)置spring.sleuth.integration.patterns模式來提供你想要包含的tracing信道名稱芥映,默認(rèn)所有的信道已被包含在內(nèi)洲尊。
Zuul
我們注冊(cè)Zuul過濾器來傳播tracing信息(請(qǐng)求頭使用tracing數(shù)據(jù)填滿),可以設(shè)置spring.sleuth.zuul.enabled為false來關(guān)閉Zuul服務(wù)奈偏。
Moreinformation
https://cloud.spring.io/spring-cloud-sleuth/#_example