[Spring Cloud] 7 Spring Cloud Sleuth

Spring Cloud Sleuth

分佈式鏈路跟蹤

Spring Cloud Sleuth是Spring Cloud的分佈式鏈路跟蹤解決方案。css

7.1 Terminology

Spring Cloud Sleuth借鑑了Dapper的術語。html

  • Span :最基本的工做單元。例如:發送一個RPC就是一個新的span,一樣一次RPC的應答也是。Span經過一個惟一的,長度64位的ID來做爲標識,另外一個64位ID用於跟蹤。Span也能夠帶有其餘數據,例如:描述,時間戳,鍵值對標籤,起始Span的ID,以及處理ID(一般使用IP地址)等等。 Span有起始和結束,他們跟蹤着時間信息。span應該都是成對出現的,有失必有終,因此一旦建立了一個span,那就必須在將來某個時間點結束它。 提示: 起始的span一般被稱爲:root span。它的id一般也被做爲一個跟蹤記錄的id。java

  • Trace :一個樹結構的Span集合。例如:在分佈式大數據存儲中,可能每一次請求都是一次跟蹤記錄。mysql

  • Annotation :用於記錄一個事件時間信息。一些基礎Annotation用於記錄請求的起始和結束,例如:git

    • cs : Client Sent 客戶端發送。這個annotation表示一個span的起始。
    • sr : Server Received 服務端接收。表示服務端接收到請求,並開始處理。若是減去cs的時間戳,則表示網絡傳輸時長。
    • ss : Server Sent 服務端完成請求處理,應答信息被髮回客戶端。若是減去sr的時間戳,則表示服務端處理請求的時長。
    • cr : Client Received 客戶端接收。標誌着Span的結束。客戶端成功的接收到服務端的應答信息。若是減去cs的時間戳,則表示請求的響應時長。

能夠經過下圖,可視化的描述了Span和Trace的概念: imagegithub

每個顏色都表示着一個span(7個span,從A到G)。他們都有這這些數據信息:web

Trace Id = X
Span Id = D
Client Sent

這表示着,這個span的Trace-Id爲X,Span-Id爲D。事件爲Client Sent。正則表達式

這些Span的上下級關係能夠經過下圖來表示: imageredis

7.2 Purpose 做用

下面內容,將以上面圖中的例子做爲原型來介紹。算法

7.2.1 Distributed tracing with Zipkin 經過Zipkin進行分佈式鏈路跟蹤

上例中總共有7個span。若是在Zipkin中,將能夠看到: image

然而當你點看一個某個跟蹤記錄時,會發現4個span: image

注意: 在跟蹤記錄的視圖中,可能會看到某些span被合併了。這也就意味着,有2個span的Server Received,Server Sent / Client Received,Client Sent發送到Zipkin,將被視爲同一個span。

爲何7個span只顯示了4個呢?

  • 1個span來自http:/start。包含這Server Received (SR) 和 Server Sent (SS) 標記。
  • 2個span來自service1service2http:/foo接口的RPC調用。包含着service1的Client Sent (CS) 和 Client Received (CR) 標記。也包含着service2的Server Received (SR) and Server Sent (SS) 標記。實際上有2個span,可是邏輯上是一個RPC調用的span。
  • 2個span來自service2service3http:/bar接口的RPC調用。包含着service2的Client Sent (CS) 和 Client Received (CR) 標記。也包含着service3的Server Received (SR) 和 Server Sent (SS) 標記。實際上有2個span,可是邏輯上是一個RPC調用的span。
  • 2個span來自service2service4http:/baz接口的RPC調用。包含着service2的Client Sent (CS) 和 Client Received (CR) 標記。也包含着service4的Server Received (SR) 和 Server Sent (SS) 標記。實際上有2個span,可是邏輯上是一個RPC調用的span。

所以,能夠統計一下實際上有多少span,1個來自http:/start,2個來自service1調用service2,2個來自service2調用service3,2個來自service2調用service4,總共7個span。

邏輯上則視爲4個span,1個外部請求service1,3個RPC調用。

7.2.2 Visualizing errors 錯誤信息的顯示

Zipkin能夠在跟蹤記錄中顯示錯誤信息。當異常拋出而且沒有捕獲,Zipkin就會自動的換個顏色顯示。在跟蹤記錄的清單中,當看到紅色的記錄時,就表示有異常拋出了。 下圖就顯示了錯誤信息: image

若是點開其中一個span,能夠看到下列信息: image

正如你看到的,能夠很清晰的顯示錯誤信息。

7.2.3 Live examples

能夠點擊下圖,查看一個在線例子:

image

點擊「dependency」圖標,能夠看到下圖: image

7.2.4 Log correlation 相關日誌

當使用grep命令對應用日誌按跟蹤ID進行過濾,例如:2485ec27856c56f4,那能夠獲得下列信息:

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中能夠看到下列信息: image

若是想要使用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+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
       }
}

注意: 若是想要在Spring Cloud Foundry中整合Grok可使用下面的規則:

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+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
       }
}

7.2.5 JSON Logback with Logstash

通常在使用Logstash時不會直接保存日誌到某個文本文件中,而是使用一個JSON文件(Logstash能夠直接使用JSON)。 那就必須添加相關依賴。

Dependencies setup 依賴設置

  • 須要確保Logback已經添加到classpath(ch.qos.logback:logback-core
  • 添加Logstash的Logback編碼器:net.logstash.logback:logstash-logback-encoder:4.6

Logback setup 設置Logback

下面會展現一個Logback配置的例子(文件名爲: logback-spring.xml

  • 應用日誌信息會被記錄成JSON格式到build/${spring.application.name}.json文件
  • 日誌還會有兩個額外的輸出:控制檯和標準日誌文件
  • 日誌格式和上一節中同樣
<?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-B3-ParentSpanId:-},%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>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"/>
		<appender-ref ref="logstash"/>
		<!--<appender-ref ref="flatfile"/>-->
	</root>
</configuration>

注意: 若是想要自定義logback-spring.xml,能夠經過bootstrap中的spring.application.name屬性來替代application的配置。不然,自定義的logback配置文件不會被加載。

7.3 Adding to the project

整合到項目中

7.3.1 Only Sleuth (log correlation) 僅包含Sleuth(日誌相關部分)

若是僅僅想使用Spring Cloud Sleuth而不想整合Ziphin,那隻須要添加Sleuth的依賴就行:spring-cloud-starter-sleuth

Maven

<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>
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-starter-sleuth依賴

Gradle

dependencyManagement { (1)
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
    }
}

dependencies { (2)
    compile "org.springframework.cloud:spring-cloud-starter-sleuth"
}
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-starter-sleuth依賴

7.3.2 Sleuth with Zipkin via HTTP 經過HTTP整合Sleuth和Zipkin

能夠經過spring-cloud-starter-zipkin來整合:

Maven

<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>
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-starter-zipkin依賴

Gradle

dependencyManagement { (1)
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
    }
}

dependencies { (2)
    compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-starter-zipkin依賴

7.3.3 Sleuth with Zipkin via Spring Cloud Stream 經過Spring Cloud Stream整合Sleuth和Zipkin

能夠經過spring-cloud-sleuth-stream來整合:

Maven

<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>
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-sleuth-stream依賴
  3. 添加spring-cloud-starter-sleuth依賴
  4. 添加Spring Cloud Stream橋接(例子中使用 Rabbit橋接)

Gradle

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)
}
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-sleuth-stream依賴
  3. 添加spring-cloud-starter-sleuth依賴
  4. 添加Spring Cloud Stream橋接(例子中使用 Rabbit橋接)

7.3.4 Spring Cloud Sleuth Stream Zipkin Collector

若是想要在Zipkin中使用Spring Cloud Sleuth 流式控制,則須要添加spring-cloud-sleuth-zipkin-stream依賴:

Maven

<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>
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-sleuth-zipkin-stream依賴
  3. 添加spring-cloud-starter-sleuth依賴
  4. 添加Spring Cloud Stream橋接(例子中使用 Rabbit橋接)

Gradle

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)
}
  1. 由Spring BOM來管理依賴版本
  2. 添加spring-cloud-sleuth-zipkin-stream依賴
  3. 添加spring-cloud-starter-sleuth依賴
  4. 添加Spring Cloud Stream橋接(例子中使用 Rabbit橋接)

而後,須要在主類上加上@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);
	}

}

7.4 Additional resources 附加資源

關於Spring Cloud Sleuth 和 Zipkin相關介紹,能夠觀看Marcin Grzejszczak的視頻

7.5 Features 特性

  • 添加trace/span ID到日誌(Slf4J MDC),這樣就能夠經過一個trace或span來提取相關的完整日誌。例如:
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 特定操做的ID
- appname 發生操做的應用名稱
- traceId 這次跟蹤的ID
- exportable 是否發送到Zipkin
  • 對於分佈式鏈路跟蹤,提供一個抽象的通用數據模型:trace,span,annotation,key-value annotation。基本基於HTrace,可是兼容Zipkin(Dapper)

  • 記錄時間信息,用於後續分析。使用Sleuth,能夠快速發現系統中的延遲緣由。Sleuth不會寫入太多日誌,不會引發過多性能開銷。

    • 包含鏈路數據,其他能夠擴展
    • 包含可選的數據展現接口,如HTTP
    • 管理卷數據支持多種採樣策略
    • 可以經過Zipkin進行數據的查詢和可視化展現
  • 可以跟蹤常規Spring應用的訪問入口和迴應點,如:servlet,filter,async endpoints,rest template,定時任務,消息渠道,zuul filters,feign客戶端等等。

  • Sleuth自帶一個默認策略,來決定跟蹤數據是經過http整合,仍是其餘通信方式來傳播消息。例如:經過HTTP方式傳輸時,報文頭兼容Zipkin。這些傳播邏輯可經過SpanInjectorSpanExtractor自定義或者擴展。

  • 對接收/丟棄的span進行簡單的統計度量。

  • 若是加入spring-cloud-sleuth-zipkin,那應用就會自動採用Zipkin兼容的方式來記錄和收集跟蹤信息。默認狀況下,會經過HTTP發送到本地Zipkin服務(端口:9411).能夠經過spring.zipkin.baseUrl來修改這一地址。

  • 若是加入If spring-cloud-sleuth-stream,那應用會採用Spring Cloud Stream的方式來記錄和收集跟蹤信息。應用會自動成爲跟蹤信息的生產者,而後將消息發送給配置的消息代理中間件(如:RabbitMQ,Kafka,Redis)。

重要: 若是使用Zipkin或者Stream,能夠配置span記錄輸出的採樣率,配置項爲spring.sleuth.sampler.percentage(默認0.1,也就是10%)。這個可能會讓開發者覺得丟失了一些span,其實否則。

注意: SLF4J MDC老是會設置,而且若是使用logback,那上面的例子中trace/span的id則會當即顯示在日誌中。其餘的日誌系統須要配置各自的格式來達到這樣的效果。默認的logging.pattern.level設置爲%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow} (這也是一個Spring Boot整合logback時有的特性)。 這就意味着,在使用SLF4J時不須要在手工配置這個格式了,自動會這樣輸出。

7.6 Sampling 採樣

在分佈式鏈路跟蹤中,跟蹤數據可能會很是大,因此採樣變的很重要。(通常來講,不須要把每個發生的動做都導出) Spring Cloud Sleuth有一個Sampler策略,能夠經過這個實現類來控制採樣算法。採樣器不會阻礙span相關id的產生,可是會對導出以及附加事件標籤的相關操做形成影響。 默認狀況下,若是一個span已經激活,則會繼續使用策略用之後續跟蹤,可是,新的span老是會標記上不用導出。

若是應用是使用這個策略,則會發現日誌中跟蹤記錄是完整的,可是遠程存儲端則不必定。 通過測試,默認值是足夠的,若是你只想使用日誌來記錄,則更好。(好比,使用ELK來進行日誌收集分析方案)。 若是須要導出span數據到Zipkin或者Spring Cloud Stream,那AlwaysSampler能夠處處所有數據,PercentageBasedSampler則會處處固定頻率的分片,能夠根據須要自行選擇使用。

注意: 在使用spring-cloud-sleuth-zipkin或者spring-cloud-sleuth-stream時,默認使用PercentageBasedSampler。能夠經過spring.sleuth.sampler.percentage對其進行配置。這個值介於0.0到1.0之間。

若是想要使用其餘策略,也很簡單,只須要:

@Bean
public Sampler defaultSampler() {
	return new AlwaysSampler();
}

7.7 Instrumentation

Spring Cloud Sleuth能夠自動的跟蹤全部Spring應用,所以,不須要作什麼額外的操做。會自動選擇相應的方法進行處理,例如:若是是一個servlet的web應用,則會使用一個Filter;若是是Spring Integration,則會使用`ChannelInterceptors。

還能夠在span標籤中自定義一些鍵。爲了限制span數據大小,默認狀況下,一次HTTP請求僅僅會帶上少許的元數據,如:狀態碼,主機地址以及URL。能夠經過spring.sleuth.keys.http.headers進行額外的配置,能夠列出想要帶上的Header名字。

注意: 標籤數據只有當Sampler容許時,纔會收集和導出。默認狀況下,是不會收集這些數據的。這些數據通常來講,量很大,也沒太多的意義。

注意: Spring Cloud Sleuth的數據採集仍是比較積極的,就是說,老是會積極的嘗試從線程上下文中獲取跟蹤數據。一樣不管是否須要導出都會捕獲時間事件。之後,可能會考慮改爲被動模式。

7.8 Span lifecycle 生命週期

經過org.springframework.cloud.sleuth.Tracer接口的api,能夠觀察到Span的各個生命週期操做:

  • start 當開始一個span時,就會分配一個名字,以及記錄啓動時間戳。
  • close 當span已經完成(記錄截止時間戳),而且若是其符合條件,則導出到Zipkin。同時從當前線程上下文中移除此span。
  • continue 做爲一個span的副本而建立的一個新的span實例。
  • detach 不會中止或者關閉span,僅僅是從當前線程上下文中移除此span。
  • create with explicit parent 建立一個新的span,並顯示指定其父span。

提示: 一般不須要去操做這些api,Spring會自動建立Tracer,開發者只須要自動注入就可使用。

7.8.1 Creating and closing spans 建立和關閉

能夠經過Tracer手動建立span:

// 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);
}

這個例子,展現瞭如何手工建立一個span實例。若是當前線程上下文中已經存在一個span了,那已存在就span會成爲新建立的span的父級。

重要: 建立完span要記住清理!若是想要發送到Zipkin,就不要忘了關閉span。

7.8.2 Continuing spans 持續

有的時候,其實不須要建立一個span,僅僅是須要在現有的span繼續一些持續的操做。例如,下列狀況:

  • AOP 若是在最對已有span的操做,進行AOP時,就不須要再額外建立了
  • Hystrix 在執行Hystrix命令時,從邏輯上講,仍然屬於當前操做中的一部分,因此,通常也不須要再次建立span。

接下來就展現,如何在現有的span上繼續處理:

Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);

使用Tracer接口:

// 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);
}

重要 建立完span要記住清理!在對現有span上繼續操做後,不要忘了最後調用detach。假如:span由線程X建立,而後它等着線程Y,Z來完成後續動做;span在線程Y,Z在完成本身的操做後要調用detach;這樣線程X關閉span時,數據纔會被收集。

7.8.3 Creating spans with an explicit parent

有的時候須要建立一個新的span,並顯示指定其父span。好比說,在一個線程中已經存在span,而後調用另外一個線程,這時候想要一個新的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。否在當關閉當前線程時,會在日誌中看到不少警告。更糟的是,不關閉span,就不會被Zipkin收集到數據。

7.9 Naming spans 具名

爲span命名,可不是個輕鬆活。Span的名字應該可以表述一個操做。名字應該是代價低廉的(好比,不帶有id)。

所以,不少span名字都是按照必定規則造出來的:

  • controller-method-name 當控制器的某個方法收到請求時:conrollerMethodName
  • async 爲一些異步操做進行包裝,如:Callable,Runnable
  • @Scheduled 使用簡單類名

對於異步處理,還能夠手工指定名字。

7.9.1 @SpanName annotation

可使用@SpanName註解來命名。

@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {

	@Override public void run() {
		// perform logic
	}
}

在這個例子中,當按照這樣的方式來執行時:

Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable());
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();

span就會被命名爲:calculateTax

7.9.2 toString() method

還有一中比較少見的方式,爲Runnable或者Callable建立一個獨立的class。最多見通常都是使用匿名類。當沒有@SpanName註解時,會檢查是否重寫了toString()方法。

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();

這樣也會建立一個名字爲calculateTax的span。

7.10 Customizations 定製化

經過SpanInjectorSpanExtractor,能夠定製span的建立和傳播。

跟蹤信息在進程間傳播,有兩種方式:

  • 經過Spring Integration
  • 經過HTTP

啓動或者合併到一個已有的跟蹤記錄時,Span的id能夠兼容Zipkin頭(不管是Message頭仍是HTTP頭)。在出站請求時,跟蹤信息會自動注入,以便下一跳的繼續跟蹤。

7.10.1 Spring Integration

對於Spring Integration能夠經過帶有Message以及MessageBuilder的特殊Bean來完成跟蹤信息構建。

@Bean
public SpanExtractor<Message> messagingSpanExtractor() {
    ...
}

@Bean
public SpanInjector<MessageBuilder> messagingSpanInjector() {
    ...
}

能夠本身實現他們,在本身class上加上@Primary就行。

7.10.2 HTTP

對於HTTP方式,則是經過HttpServletRequest來完成跟蹤信息的構建。

@Bean
public SpanExtractor<HttpServletRequest> httpServletRequestSpanExtractor() {
    ...
}

能夠本身實現他們,在本身class上加上@Primary就行。

7.10.3 Example

假如不使用標準的Zipkin方式來命名HTTP頭:

  • trace id 命名爲:correlationId
  • span id 命名爲:mySpanId

SpanExtractor以下:

static class CustomHttpServletRequestSpanExtractor
		implements SpanExtractor<HttpServletRequest> {

	@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();
	}
}

而後,能夠這樣註冊它:

@Bean
@Primary
SpanExtractor<HttpServletRequest> customHttpServletRequestSpanExtractor() {
	return new CustomHttpServletRequestSpanExtractor();
}

Spring Cloud Sleuth處於安全的緣由,不會在Http Response上,加上trace/span相關的頭信息。若是須要加上,則能夠自定義一個SpanInjector,而後配置一個Servlet Filter來完成:

static class CustomHttpServletResponseSpanInjector
		implements SpanInjector<HttpServletResponse> {

	@Override
	public void inject(Span span, HttpServletResponse carrier) {
		carrier.addHeader(Span.TRACE_ID_NAME, span.traceIdString());
		carrier.addHeader(Span.SPAN_ID_NAME, Span.idToHex(span.getSpanId()));
	}
}

static class HttpResponseInjectingTraceFilter extends GenericFilterBean {

	private final Tracer tracer;
	private final SpanInjector<HttpServletResponse> spanInjector;

	public HttpResponseInjectingTraceFilter(Tracer tracer, SpanInjector<HttpServletResponse> spanInjector) {
		this.tracer = tracer;
		this.spanInjector = spanInjector;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		Span currentSpan = this.tracer.getCurrentSpan();
		this.spanInjector.inject(currentSpan, response);
		filterChain.doFilter(request, response);
	}
}

而後,能夠這樣註冊它們:

@Bean
SpanInjector<HttpServletResponse> customHttpServletResponseSpanInjector() {
	return new CustomHttpServletResponseSpanInjector();
}

@Bean
HttpResponseInjectingTraceFilter responseInjectingTraceFilter(Tracer tracer) {
	return new HttpResponseInjectingTraceFilter(tracer, customHttpServletResponseSpanInjector());
}

7.10.4 Custom SA tag in Zipkin 在Zipkin中定製SA標籤

有的時候想要手工建立一個Span,用於跟蹤一個外部服務的調用。那可使用peer.service標籤來建立span,標籤中能夠包含想要調用的值。下面這個例子就是擴展調用Redis服務:

org.springframework.cloud.sleuth.Span newSpan = tracer.createSpan("redis");
try {
	newSpan.tag("redis.op", "get");
	newSpan.tag("lc", "redis");
	newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_SEND);
	// call redis service e.g
	// return (SomeObj) redisTemplate.opsForHash().get("MYHASH", someObjKey);
} finally {
	newSpan.tag("peer.service", "redisService");
	newSpan.tag("peer.ipv4", "1.2.3.4");
	newSpan.tag("peer.port", "1234");
	newSpan.logEvent(org.springframework.cloud.sleuth.Span.CLIENT_RECV);
	tracer.close(newSpan);
}

重要: 記住不要同時添加peer.serviceSA標籤!只須要加上peer.service就行。

7.10.5 Custom service name 定製服務名

默認狀況下,Sleuth會假定span須要發送到Zipkin的spring.application.name服務。在實際使用時,可能不想這樣。可能須要指定一個服務來接收某個應用的所有span。其實這樣只須要簡單配置一下就行,如:

spring.zipkin.service.name: foo

7.10.6 Host locator 主機定位

爲了能夠跨主機來跟蹤,須要對主機名和端口進行抉擇。默認的策略是經過server的配置屬性。若是沒有配置,則會嘗試從網絡中獲取。

若是啓用了服務發現,且服務實例已經註冊了,那就須要設置這個配置項:

spring.zipkin.locator.discovery.enabled: true

7.11 Span Data as Messages

當引入spring-cloud-sleuth-stream依賴並加上Channel Binder(如 :spring-cloud-starter-stream-rabbit或者spring-cloud-starter-stream-kafka)後,就能夠經過Spring Cloud Stream來堆積和發送span數據了。這樣就會自動產生消息,而且消息負載會是Spans類型。

7.11.1 Zipkin Consumer

有一個專門的註解來轉換消息,可讓Span數據推送到Zipkin的SpanStore中。如:

@SpringBootApplication
@EnableZipkinStreamServer
public class Consumer {
	public static void main(String[] args) {
		SpringApplication.run(Consumer.class, args);
	}
}

這樣Span數據就能夠經過Spring Cloud Stream來轉發給Zipkin了。若是想要UI界面,再加上下面這個依賴就行:

<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>

這樣就擁有了一個Zipkin服務,默認端口爲9411。

默認的SpanStore是經過內存實現的。也可使用MySQL,加入spring-boot-starter-jdbc依賴就行。具體配置以下:

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,因此,將會以標準的Zipkin服務接口的方式來處理,即:經過HTTP方式收集span數據,經過Zipkin Web來進行查詢。

7.11.2 Custom Consumer

跟蹤信息的自定義消費端也比較簡單,可使用spring-cloud-sleuth-stream來綁定到SleuthSink。例如:

@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class Consumer {

    @ServiceActivator(inputChannel = SleuthSink.INPUT)
    public void sink(Spans input) throws Exception {
        // ... process spans
    }
}

注意: 上例中,明確排除了SleuthStreamAutoConfiguration,所以,應用自己就不會發送消息了,但這也是可選的,實際使用中,能夠根據須要不排除。

7.12 Metrics

當前版本的Spring Cloud Sleuth只是對span進行簡單的度量。主要是經過Spring Boot的metrics機制,對span的接收和丟棄數量進行了度量。每次sapn發送到Zipkin時,接收數量就會遞增。當有錯誤時,丟棄數量就會遞增。

7.13 Integrations 整合

7.13.1 Runnable and Callable

若是是使用Runnable或者Callable來包裝邏輯代碼。能夠這樣:

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<String> callable = new Callable<String>() {
	@Override
	public String call() throws Exception {
		return someLogic();
	}

	@Override
	public String toString() {
		return "spanNameFromToStringMethod";
	}
};
// Manual `TraceCallable` creation with explicit "calculateTax" Span name
Callable<String> 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<String> traceCallableFromTracer = tracer.wrap(callable);

這樣每次執行都會有新的Span的建立和關閉。

7.13.2 Hystrix

7.13.2.1 Custom Concurrency Strategy 定製併發策略

能夠註冊一個自定義的HystrixConcurrencyStrategy,它經過TraceCallable能夠包裝Sleuth中全部的Callable實例。這個策略,會自行判斷在以前的Hystrix命令是否已經開始跟蹤,來決定是建立仍是延續使用span。 也能夠經過設置spring.sleuth.hystrix.strategy.enabledfalse來關閉這個策略。

7.13.2.2 Manual Command setting

假設有下面這樣的HystrixCommand:

HystrixCommand<String> hystrixCommand = new HystrixCommand<String>(setter) {
	@Override
	protected String run() throws Exception {
		return someLogic();
	}
};

爲了跟蹤,能夠用TraceCommand對其進行必定的包裝:

TraceCommand<String> traceCommand = new TraceCommand<String>(tracer, traceKeys, setter) {
	@Override
	public String doRun() throws Exception {
		return someLogic();
	}
};

7.13.3 RxJava

建議自定義一個RxJavaSchedulersHook,它使用TraceAction來包裝實例中全部的Action0。這個鉤子對象,會根據以前調度的Action是否已經開始跟蹤,來決定是建立仍是延續使用span。能夠經過設置spring.sleuth.rxjava.schedulers.hook.enabledfalse來關閉這個對象的使用。

能夠定義一組正則表達式來對線程名進行過濾,來選擇哪些線程不須要跟蹤。可使用逗號分割的方式來配置spring.sleuth.rxjava.schedulers.ignoredthreads屬性。

7.13.4 HTTP integration

這個特性的開啓,經過spring.sleuth.web.enabled屬性。當不想使用時,設置爲false就行。

7.13.4.1 HTTP Filter

經過TraceFilter能夠對全部入站請求進行跟蹤。這時候,Span的名字爲http:加上請求的路徑。例如,若是請求是/foo/bar,那span名字就是http:/foo/bar。經過spring.sleuth.web.skipPattern配置項,能夠配置一個URI規則來跳過監控。若是classpath中有一個ManagementServerProperties,其中contextPath也不會被跟蹤。

7.13.4.2 HandlerInterceptor

若是須要對span名字進行進一步的控制,可使用TraceHandlerInterceptor,它會對已有的HandlerInterceptor進行包裝,或者直接添加到已有的HandlerInterceptors中。 TraceHandlerInterceptor會在HttpServletRequest中添加一個特別的request attribute。若是TraceFilter沒有發現這個屬性,就會建立一個額外的「fallback」(保底)span,這樣確保跟蹤信息完整。

7.13.4.3 Async Servlet support

若是控制器返回了一個Callable或者WebAsyncTask,Spring Cloud Sleuth會延續已有的span,而不是建立一個新的span。

7.13.5 HTTP client integration

7.13.5.1 Synchronous Rest Template

重要: 一個AsyncRestTemplateBean被註冊時會有一個版本概念。若是須要本身的Bean來替代TraceAsyncRestTemplate。最好的方式是自定義一個ClientHttpRequestFactory以及AsyncClientHttpRequestFactory。若是須要本身的AsyncRestTemplate而又不想包裝它,那這個就不會被跟蹤。

自定義span在發送和接收請求時的建立/關閉邏輯,能夠自定義ClientHttpRequestFactoryAsyncClientHttpRequestFactoryBean來達到這個目的。記住使用那些能兼容跟蹤的實例(不要忘了在TraceAsyncListenableTaskExecutor中包裝一個ThreadPoolTaskScheduler來使用)。

例如:自定義請求工廠:

@EnableAutoConfiguration
@Configuration
public static class TestConfiguration {

	@Bean
	ClientHttpRequestFactory mySyncClientFactory() {
		return new MySyncClientHttpRequestFactory();
	}

	@Bean
	AsyncClientHttpRequestFactory myAsyncClientFactory() {
		return new MyAsyncClientHttpRequestFactory();
	}
}

若是須要阻止AsyncRestTemplate特性,能夠設置spring.sleuth.web.async.client.enabledfalse

若是須要禁用默認的TraceAsyncClientHttpRequestFactoryWrapper,能夠設置spring.sleuth.web.async.client.factory.enabledfalse

若是不想建立AsyncRestClient,能夠設置spring.sleuth.web.async.client.template.enabledfalse

7.13.6 Feign

默認狀況下,Spring Cloud Sleuth提供了一個TraceFeignClientAutoConfiguration來整合Feign。若是須要禁用的話,能夠設置spring.sleuth.feign.enabledfalse。若是禁用,與Feign相關的機制就不會發生。

Feign部分功能是經過FeignBeanPostProcessor來完成的。能夠設置spring.sleuth.feign.processor.enabledfalse來禁用這個類。若是禁用,那Spring Cloud Sleuth就不會執行自定義的Feign組件。不過,全部默認的Feign組件仍是有效的。

7.13.7 Asynchronous communication

7.13.7.1 @Async annotated methods

在Spring Cloud Sleuth中,有相應的機制來處理異步組件的跟蹤,這樣在不一樣的線程之間也可以進行跟蹤。能夠設置spring.sleuth.async.enabledfalse來關閉。

若是在方法上加上@Async,那會自動的建立一個新的span,並帶有下列特性:

  • span 名字會被命名爲被註解的方法名
  • span 標籤中會自動帶上方法的類名和方法名

7.13.7.2 @Scheduled annotated methods

在Spring Cloud Sleuth中,有相應的機制來處理調度方法的執行,這樣在不一樣的線程之間也可以進行跟蹤。能夠設置spring.sleuth.scheduled.enabledfalse來關閉。

若是在方法上加上@Scheduled,那就會自動建立一個新的span,並帶有下列特性:

  • span 名字會被命名爲被註解的方法名
  • span 標籤中會自動帶上方法的類名和方法名

若是不須要跟蹤某些@Scheduled,能夠在spring.sleuth.scheduled.skipPattern設置一些正則表達式來過濾一些class。

提示: 若是一塊兒使用spring-cloud-sleuth-streamspring-cloud-netflix-hystrix-stream,那span會被每個Hystrix metrics建立併發送到Zipkin。這可能不是你想要的。但是進行以下設置,來阻止此行爲:spring.sleuth.scheduled.skipPattern=org.springframework.cloud.netflix.hystrix.stream.HystrixStreamTask

7.13.7.3 Executor, ExecutorService and ScheduledExecutorService

Sleuth自己就提供了LazyTraceExecutor,TraceableExecutorService以及TraceableScheduledExecutorService。這些線程池對於每一次新任務的提交,調用或者調度都會建立新的span。

下面的列子,展現瞭如何在使用CompletableFuture時經過TraceableExecutorService來處理跟蹤信息。

CompletableFuture<Long> 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"));

7.13.8 Messaging 消息

Spring Cloud Sleuth自己就整合了Spring Integration。它發佈/訂閱事件都是會建立span。能夠設置spring.sleuth.integration.enabledfalse來禁用這個機制。

Spring Cloud Sleuth直到1.0.4版本,使用消息時,仍是會發送一些無效的跟蹤頭。這些頭實際上和HTTP頭的命名同樣(都帶有-分隔符)。爲了向下兼容,從1.0.4版本開始,有效和無效頭都會發送。到Spring Cloud Sleuth 1.1版本,將會移除那些不建議使用的頭。

從1.0.4版本開始,能夠經過spring.sleuth.integration.patterns配置哪些消息通道須要跟蹤。默認狀況下,全部的消息通道都會被跟蹤。

7.13.9 Zuul

Sleuth會註冊一些Zuul Filter,用於傳播跟蹤信息(在請求頭中帶上跟蹤信息)。能夠設置spring.sleuth.zuul.enabledfalse來關閉。

7.14 Running examples

能夠找到一些部署在Pivotal Web Services中的例子。能夠在下列連接中找到:

相關文章
相關標籤/搜索