分布式服務(wù)隨著業(yè)務(wù)的發(fā)展,系統(tǒng)規(guī)模也會(huì)變得越來(lái)越大,各服務(wù)之間的調(diào)用關(guān)系也變得越來(lái)約復(fù)雜。通常一個(gè)由客戶端發(fā)起的請(qǐng)求在后端系統(tǒng)中會(huì)經(jīng)過(guò)多個(gè)不同的微服務(wù)調(diào)用來(lái)協(xié)同產(chǎn)生最后的請(qǐng)求結(jié)果,在復(fù)雜的微服務(wù)架構(gòu)系統(tǒng)中,幾乎每一個(gè)前端請(qǐng)求都有可能引起請(qǐng)求最后的失敗。這時(shí)候,對(duì)于每一個(gè)依賴服務(wù)出現(xiàn)延遲過(guò)高或者錯(cuò)誤的時(shí)候都有可能引起服務(wù)最后的失敗。這時(shí)候,對(duì)于每個(gè)請(qǐng)求,全鏈路調(diào)用的跟蹤就變得越來(lái)越重要,通過(guò)實(shí)現(xiàn)對(duì)請(qǐng)求調(diào)用的根據(jù)可以幫助我們快速發(fā)現(xiàn)錯(cuò)誤根源以及監(jiān)控分析每條請(qǐng)求鏈路上的性能瓶頸。針對(duì)上面問(wèn)題,Spring Cloud Sleuth提供了一套完整的解決方案
快速入門
在引入Sleuth之前,我們依賴本人其他博客內(nèi)容,構(gòu)建一些基礎(chǔ)設(shè)施和引用
.服務(wù)注冊(cè)中心Eureka
.微服務(wù)應(yīng)用:trace-1,實(shí)現(xiàn)一個(gè)Rest接口/trace-1,調(diào)用接口后將觸發(fā)對(duì)trace2應(yīng)用的調(diào)用,具體實(shí)現(xiàn)如下:
pom依賴:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
.應(yīng)用trace1、trace2的application.yml配置:
# 只列出了trace1應(yīng)用的配置文件,trace2應(yīng)用配置內(nèi)容類似
server:
port: 8201
spring:
application:
name: trace1
eureka:
instance:
instance-id: trace:8201
hostname: trace1
client:
fetch-registry: true
register-with-eureka: true
serviceUrl:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
.trace1、trace2應(yīng)用啟動(dòng)類
將trace1、trace2應(yīng)用注冊(cè)到Eureka注冊(cè)中心
@SpringBootApplication
@EnableEurekaClient
public class Tracing1Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Tracing1Application.class, args);
}
}
.trace1應(yīng)用REST接口
@RestController
@Slf4j
public class Trace {
@Autowired
RestTemplate restTemplate;
@GetMapping("/trace-1")
public String trace() {
log.info("=====call trace-1====");
return restTemplate.getForEntity("http://trace2/trace-2",String.class).getBody();
}
}
.trace2應(yīng)用REST接口
@RestController
@Slf4j
public class Trace {
@Autowired
RestTemplate restTemplate;
@GetMapping("/trace-2")
public String trace() {
log.info("=====call trace-2====");
return "Trace";
}
}
.瀏覽器調(diào)用http://localhost:8201/trace-1,顯示結(jié)果如下:
# trace1
2018-10-28 12:57:05.738 INFO [trace1,fc2ec50d569fb572,fc2ec50d569fb572,false] 2423 --- [nio-8201-exec-6] s.tracing1.traceController.Trace : =====call trace-1====
# trace2
2018-10-28 12:57:11.227 INFO [trace2,fc2ec50d569fb572,0e335c29b8314c9d,false] 2431 --- [nio-8202-exec-3] s.tracing2.traceController.Trace : =====call trace-2====
從控制臺(tái)輸出的內(nèi)容中,我們可以看到打印出了[trace1,fc2ec50d569fb572,fc2ec50d569fb572,false]信息,這些元素正式分布式跟蹤的重要組成部分,各元素含義如下:
.第一個(gè)值:trace1,應(yīng)用的名稱,application.yml文件中對(duì)應(yīng)的spring.application.name
.第二個(gè)值:fc2ec50d569fb572,Spring Cloud Sleuth生成的Trace ID,用于表示一條請(qǐng)求鏈路。一個(gè)請(qǐng)求鏈路可以包含一個(gè)Trace ID和多個(gè)Span ID
.第三個(gè)值:0e335c29b8314c9d,Span ID,它表示一個(gè)基本的工作單。
.第四個(gè)值:false,表示是否將該信息輸出到Zipkin等服務(wù)中來(lái)收集和展示
.跟蹤原理
也可以通過(guò)在trace2的/trace-2方法中增加下面的輸出,打印出traceId和spanId
@RestController
@Slf4j
public class Trace {
@Autowired
RestTemplate restTemplate;
@GetMapping("/trace-2")
public String trace(HttpServletRequest request) {
log.info("=====call trace-2====,Trace ID={},Span ID={}",
request.getHeader("X-B3-TraceId"),
request.getHeader("X-B3-SpanId"));
return "Trace";
}
}
# trace1應(yīng)用輸出
2018-10-28 13:37:38.477 INFO [trace1,8556870ade8f4c38,8556870ade8f4c38,false] 2423 --- [io-8201-exec-10] s.tracing1.traceController.Trace : =====call trace-1====
# trace2應(yīng)用輸出
2018-10-28 13:37:38.584 INFO [trace2,8556870ade8f4c38,7ee6e8ed174f3b18,false] 2815 --- [nio-8202-exec-1] s.tracing2.traceController.Trace : =====call trace-2====,Trace ID=8556870ade8f4c38,Span ID=7ee6e8ed174f3b18
.X-B3-TraceId:一條鏈路(Trace)的唯一標(biāo)識(shí)
.X-B3-SpanId:一個(gè)工作單元(Span)的唯一標(biāo)識(shí)
.X-B3-ParentSpanId:標(biāo)示當(dāng)前工作單元所屬的上一個(gè)工作單元,Root Span(請(qǐng)求鏈路的第一個(gè)工作單元)的該值為空。
.X-B3-Sampled:是否被抽樣輸出的標(biāo)志,1標(biāo)示需要輸出,0標(biāo)示不需要
.X-Span-Name:工作單元名稱