1. 程式人生 > >Spring Cloud技術分析(3)- spring cloud sleuth

Spring Cloud技術分析(3)- spring cloud sleuth

地址:http://tech.lede.com/

1. 目的

  • 提供鏈路追蹤。通過sleuth可以很清楚的看出一個請求都經過了哪些服務。可以很方便的理清服務間的呼叫關係。
  • 視覺化錯誤。對於程式未捕捉的異常,可以在zipkin介面上看到。
  • 分析耗時。通過sleuth可以很方便的看出每個取樣請求的耗時,分析出哪些服務呼叫比較耗時。當服務呼叫的耗時隨著請求量的增大而增大時,也可以對服務的擴容提供一定的提醒作用。
  • 優化鏈路。對於頻繁地呼叫一個服務,或者並行地呼叫等,可以針對業務做一些優化措施。

2. 應用程式整合spring cloud sleuth

spring cloud sleuth可以結合zipkin,將資訊傳送到zipkin,利用zipkin的儲存來儲存資訊,利用zipkin ui來展示資料。同時也可以只是簡單的將資料記在日誌中。

2.1 僅僅使用sleuth+log配置

maven配置

1
2
3
4
5
6
7
8
9
10
11
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-dependencies</artifactId>
	<version>Camden.SR6</version>
	<type>pom</type>
	<scope>import</scope>
</dependency
>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency>

這種方式只需要引入jar包即可。如果配置log4j,這樣會在打印出如下的日誌:

2017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 — [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 2: Handling print
2017-04-08 23:56:50.459 INFO [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 8764 — [nio-8080-exec-1] demo.JpaSingleDatasourceApplication : Step 1: Handling home

比原先的日誌多出了 [bootstrap,38d6049ff0686023,d1b8b0352d3f6fa9,false] 這些內容,[appname,traceId,spanId,exportable]。

  • appname:服務名稱
  • traceId\spanId:鏈路追蹤的兩個術語,後面有介紹
  • exportable:是否是傳送給zipkin

2.2 sleuth+zipkin+http

sleuth收集跟蹤資訊通過http請求發給zipkin。這種需要啟動一個zipkin,zipkin用來儲存資料和展示資料。

大體流程圖

1-2

BlockingQueue的大小sleuth寫死了為1000。當佇列滿了還往裡放的話,sleuth只是加了個記錄處理。

應用程式配置

maven引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-dependencies</artifactId>
	<version>Camden.SR6</version>
	<type>pom</type>
	<scope>import</scope>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
配置檔案配置
1
2
3
spring.sleuth.sampler.percentage=0.1  取樣率 
spring.zipkin.baseUrl=http://zipkin.xxx.com 傳送到zipkinServer的url
spring.zipkin.enabled=true

zipkin

maven引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
    <!--<version>1.40.2</version>-->
</dependency>
spring boot程式
1
2
3
4
5
6
7
8
9
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@EnableZipkinServer
public class SleuthServerApplication
{
	public static void main(String[] args)
{
		SpringApplication.run(SleuthServerApplication.class, args);
	}
}
儲存配置

zipkin的儲存包括mysql、es、cassadra。如果不配置儲存的話,預設是在記憶體中的。如果在記憶體中的話,當重啟應用後,資料就會丟失了。

mysql儲存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
  application:
    name: sleuth-zipkin-http
  datasource:
    schema: classpath:/mysql.sql
    url: jdbc:mysql://192.168.3.3:2222/zipkin
    driverClassName: com.mysql.jdbc.Driver
    username: app
    password: %jdbc-1.password%
    # Switch this on to create the schema on startup:
    initialize: true
    continueOnError: true
  sleuth:
    enabled: false

# default is mem (in-memory)
zipkin:
	storage:
	   type: mysql

mysql的指令碼在zipkin包裡已經提供了,只需要執行一下就可以了。

es儲存
1
2
3
4
5
6
7
8
9
zipkin:
  storage:
    type: elasticsearch
    elasticsearch:
      cluster: ${ES_CLUSTER:elasticsearch}
      hosts: ${ES_HOSTS:localhost:9300}
      index: ${ES_INDEX:zipkin}
      index-shards: ${ES_INDEX_SHARDS:5}
      index-replicas: ${ES_INDEX_REPLICAS:1}

2.3 sletuh+streaming+zipkin

這種方式通過spring cloud streaming將追蹤資訊傳送到zipkin。spring cloud streaming目前只有kafka和rabbitmq的binder。以kafka為例:

大體流程

1-1

Collector是原始碼的類名。Collector從訊息中介軟體中讀取資料並存儲到db和es中。

應用程式配置

maven引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-dependencies</artifactId>
	<version>Camden.SR6</version>
	<type>pom</type>
	<scope>import</scope>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-sleuth-stream</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>

zipkin

maven引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
    <!--<version>1.40.2</version>-->
</dependency>
spring boot程式
1
2
3
4
5
6
7
8
9
10
11
@EnableZipkinStreamServer
@EnableBinding(SleuthSink.class)
@SpringBootApplication(exclude = SleuthStreamAutoConfiguration.class)
@MessageEndpoint
public class SleuthServerApplication
{
	public static void main(String[] args)
{
		SpringApplication.run(SleuthServerApplication.class, args);
	}
}
配置
1
2
3
4
5
stream:
  kafka:
    binder:
      brokers: xxx:9098,xxx:9098,xxx:9098
      zk-nodes: xxx:2186,xxx:2186,xxx:2186,xxx:2186,xxx:2186

儲存配置和上面的一樣。

3. sleuth支援

通過sleuth-core的jar包結構,可以很明顯的看出,sleuth可以進行鏈路追蹤的程式碼:

1-1

web下面包括http和feign。

3.1 http

可以通過spring.sleuth.web.enabled=false來禁止這種型別的鏈路追蹤。http支援實現的關鍵類是 TraceFilter和TraceHandlerInterceptor。

  • TraceFilter:對入站的請求加上X-B3-SpanId、X-B3-TraceId等屬性,來對請求進行鏈路追蹤。這時候,Span的名字為http:加上請求的路徑。例如,如果請求是/foo/bar,那span名字就是http:/foo/bar。
  • TraceHandlerInterceptor:如果需要對span名字進行進一步的控制,可以使用TraceHandlerInterceptor,它會對已有的HandlerInterceptor進行包裝,或者直接新增到已有的HandlerInterceptors中。TraceHandlerInterceptor會在HttpServletRequest中新增一個特別的request attribute。如果TraceFilter沒有發現這個屬性,就會建立一個額外的“fallback”(保底)span,這樣確保跟蹤資訊完整。

3.2 runnable、callable、Executor

可以通過 TraceRunnable 和 TraceCallable來對runnable和callable進行包裝。也可以用LazyTraceExecutor來代替java的Executor。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Autowired
private BeanFactory beanFactory;
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2);
@RequestMapping("/service1")
public String service1()
{

	Runnable runnable = () ->
	{
		try
		{
			Thread.sleep(1000);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	};
	Executor executor = new LazyTraceExecutor(beanFactory, EXECUTOR);
	executor.execute(runnable);
	return "hello world";
}

這樣每次執行都有span的新建和銷燬。通過LazyTraceExecutor原始碼可以很輕鬆的看到:

1
2
3
4
5
6
7
8
9
10
@Override
public void run() {
	Span span = startSpan();
	try {
		this.getDelegate().run();
	}
	finally {
		close(span);
	}
}

3.3 feign

預設情況下,Spring Cloud Sleuth提供了一個TraceFeignClientAutoConfiguration來整合Feign。如果需要禁用的話,可以設定spring.sleuth.feign.enabledfalse。如果禁用,與Feign相關的機制就不會發生。

3.4 RxJava

建議自定義一個RxJavaSchedulersHook,它使用TraceAction來包裝例項中所有的Action0。這個鉤子物件,會根據之前排程的Action是否已經開始跟蹤,來決定是建立還是延續使用span。可以通過設定spring.sleuth.rxjava.schedulers.hook.enabledfalse來關閉這個物件的使用。可以定義一組正則表示式來對執行緒名進行過濾,來選擇哪些執行緒不需要跟蹤。可以使用逗號分割的方式來配置spring.sleuth.rxjava.schedulers.ignoredthreads屬性。

3.5 messaging

Spring Cloud Sleuth本身就整合了Spring Integration。它釋出/訂閱事件都是會建立span。可以設定spring.sleuth.integration.enabled=false來禁用這個機制。

4 基本概念

因為sleuth是根據google的dapper論文而來的,所以用的術語和dapper一樣。

1-2

4.1 術語

  • span:最基本的工作單元。由spanId來標誌。Span也可以帶有其他資料,例如:描述,時間戳,鍵值對標籤,起始Span的ID,以及處理ID(通常使用IP地址)等等。 Span有起始和結束,他們跟蹤著時間資訊。span應該都是成對出現的,所以一旦建立了一個span,那就必須在未來某個時間點結束它。起始的span通常被稱為:root span。它的id通常也被作為一個跟蹤記錄的id。
  • traceId:一個樹結構的span集合。把相同traceId的span串起來。
  • annotation:用於記錄一個事件時間資訊。
    • cs:client send。客戶端傳送,一個span的開始
    • cr:client receive。客戶端接收。一個span的結束
    • ss:server send。伺服器傳送
    • sr:server receive。伺服器接收,開始處理。
    • sr-cs和cr-ss:表示網路傳輸時長
    • ss-sr:表示服務端處理請求的時長
    • cr-cs:表示請求的響應時長

4.2 取樣率

如果服務的流量很大,全部採集對儲存壓力比較大。這個時候可以設定取樣率,sleuth 可以通過設定 spring.sleuth.sampler.percentage=0.1。不配置的話,預設取樣率是0.1。也可以通過實現bean的方式來設定取樣為全部取樣(AlwaysSampler)或者不採樣(NeverSampler):如

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

sleuth取樣演算法的實現是 Reservoir sampling(水塘抽樣)。實現類是 PercentageBasedSampler。

4.3 traceId和spanId的生成問題

traceId和spanId的生成,sleuth是通過java 的Random類的nextLong方法生成的。這樣的話就存在traceId存在一樣的情況,不知道為什麼要這麼設計。

參考資料