1.簡介
微服務架構是一個分布式結構架構,一般按業務劃分為多個服務單元,服務單元間會通信進行資料互動。由于服務單元數量單元多,業務複雜,如果出現了異常或者錯誤,很難去定位。是以微服務架構中,必須實作分布式鍊路追蹤,去跟進一個請求到底有哪些服務參與,參與的順序又是怎樣的,進而達到每個請求的步驟清晰可見,出了問題,很快定位。
Spring Cloud Sleuth則實作了分布式跟蹤解決方案。內建Zipkin進行日志鍊路檢視,非常友善。
官網位址:https://cloud.spring.io/spring-cloud-sleuth
2.術語
Spring Cloud Sleuth借鑒了Dapper的術語。
Span:基本工作機關。例如,發送RPC是一個新的跨度,就像發送響應到RPC一樣。跨度由跨度的唯一64位ID和跨度所屬的跟蹤的另一個64位ID辨別。跨區還具有其他資料,例如描述,帶有時間戳的事件,鍵值注釋(标簽),引起跨度的跨區的ID和程序ID(通常為IP位址)。
跨度可以啟動和停止,并且可以跟蹤其時序資訊。建立跨度後,您必須在将來的某個時間點将其停止。
Trace:一組形成樹狀結構的跨度。例如,如果您運作分布式大資料存儲,則跟蹤可能是由PUT請求形成的。
Annotation:用來及時記錄一個事件的,一些核心注解用來定義一個請求的開始和結束 。這些注解包括以下:
cs:Client Sent -用戶端發送一個請求,這個注解描述了這個Span的開始
sr:Server Received -服務端獲得請求并準備開始處理它,如果将其sr減去cs時間戳便可得到網絡傳輸的時間。
ss:Server Sent (服務端發送響應)–該注解表明請求處理的完成(當請求傳回用戶端),如果ss的時間戳減去sr時間戳,就可以得到伺服器請求的時間。
cr:Client Received (用戶端接收響應)-此時Span的結束,如果cr的時間戳減去cs時間戳便可以得到整個請求所消耗的時間。
3.架構
下圖顯示了Span和Trace以及Zipkin在系統中的追蹤:

4.實戰
4.1.Zipkin服務端
從github上git clone項目,本地運作。後續可以直接下載下傳jar運作,可另行百度。
git位址:https://github.com/openzipkin/zipkin.git
Zipkin接收spring cloud sleuth傳過來的追蹤資料預設是儲存在記憶體中,直接啟動即可。
若需要儲存到elasticsearch,需部署啟動elasticsearch後,在zipkin-server中配置如下:
1.新增配置檔案zipkin-server-param.yml,内容如下:
STORAGE_TYPE: elasticsearch
ES_HOSTS: localhost:9200
2.在zipkin-server.yml中增加配置:
spring.profiles.include: shared,param
3.重新啟動,則資料儲存至elasticsearch中。
啟動成功後,浏覽器通路http://127.0.0.1:9411即可通路追蹤頁面
4.2.spring boot服務
4.2.1.建構一個測試的spring boot服務,pom.xml中引用依賴
<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>
4.2.2配置檔案application.yml中内容分如下
spring:
sleuth:
enabled: true
trace-id128: true
sampler:
probability: 1.0
application:
name: teseService
zipkin:
service:
name: myService
# zipkin:
# base-url: http://localhost:9411
logging:
level:
org.springframework.web: debug
server:
port: 8080
4.2.3主程式類
@SpringBootApplication
@ComponentScan(basePackages = {"com.sky"})
@EnableScheduling
public class ServicecombZipkinApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
/**
* 提取器 優先高于TagValueExpressionResolver
* @return
*/
@Bean(name = "myCustomTagValueResolver")
public TagValueResolver TagValueResolver(){
return parameter -> {
if (parameter == null) {
return null;
}
if(parameter instanceof List){
return String.format("list's size is %d",((List)parameter).size());
}
if(parameter instanceof Map){
return String.format("map's size is %d",((Map)parameter).size());
}
return (String)parameter;
};
}
/**
* tag 值解析表達式
* @return
*/
@Bean
public TagValueExpressionResolver tagValueExpressionResolver(){
return (expression, parameter) -> {
System.out.println(expression);
System.out.println(parameter);
return expression;
};
}
@Autowired
private BeanFactory beanFactory;
@Bean("lazyTraceExecutor")
public Executor getAsyncExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("my-executor-");
executor.initialize();
return new LazyTraceExecutor(this.beanFactory,executor);
}
@Bean("traceableExecutorService")
public TraceableExecutorService traceableExecutorService(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,50,1000, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10));
return new TraceableExecutorService(this.beanFactory, threadPoolExecutor);
}
public static void main(String[] args) {
SpringApplication.run(ServicecombZipkinApplication.class, args);
}
}
注意:Spring cloud sleuth要追蹤日志,需要時spring管理的bean才被追蹤到,是以使用過程中,要被追蹤的操作需要注冊bean到spring中。
4.2.4建立一個TraceService.java,負責追蹤的一些操作全封裝在這個bean中
import brave.Span;
import brave.Tracer;
import brave.propagation.ExtraFieldPropagation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TracerService {
@Autowired(required = false)
private Tracer tracer;
public Span currentSpan() {
if (tracer == null) {
return null;
}
return tracer.currentSpan();
}
public void addTagToCurrentSpan(String key, String value) {
Span span = this.currentSpan();
if (span == null) {
return;
}
// span.tag("baz",
// ExtraFieldPropagation.get(span.context(), "foo"));
span.tag(key, value);
}
public void addToContext(String key,String value){
Span span = this.currentSpan();
if(span == null){
return;
}
ExtraFieldPropagation.set(span.context(), key, value);
}
public Tracer tracer(){
return tracer;
}
}
注意:@Autowired(required = false) private Tracer tracer;
以免關閉功能時spring.sleuth.enabled=false bean注入報錯。
4.2.5測試服務TestService.java
@Component
public class TestService {
@Autowired
TestService tranceService;
@Autowired
TracerService tracer;
@Autowired
Util util;
@Qualifier("lazyTraceExecutor")
@Autowired
Executor executor;
@NewSpan(name = "服務測試")
public void test(@SpanTag(key = "tag", expression = "'hello' + tag + ' characters'") String s) {
System.out.println("------------------------------->test");
tracer.addToContext("baz", "父類的foo");
executor.execute(() -> System.out.println("asd---------"));
executor.execute(() -> tranceService.thread("thread----"));
tranceService.test1("s");
}
@NewSpan("asyncSpan")
public void thread(@SpanTag(key = "param") String param) {
System.out.println("thread----->" + param);
this.tranceService.test1("oo");
}
@NewSpan(name = "服務測試1")
// @SpanName("服務測試2")
// @ContinueSpan(log = "aa")
public void test1(@SpanTag(key = "s", expression = "'hello' + ' characters'", resolver = TagValueResolver.class) String s) {
util.test("cesh");
System.out.println("-------------------------->test1" + s);
tracer.addTagToCurrentSpan("success", "成功l");
throw new NullPointerException("空指針。。。");
}
@Scheduled(cron = "0/15 * * * * *")
@NewSpan("測試定時器")
public void schedule() {
tranceService.test("scheme");
}
}
調用本類中的方法時,需在本類中注入@Autowired TestService tranceService;
在通過tranceService.test("");這樣,才會被追蹤到span中。
支援@Scheduled方式的定時器、LazyTraceExecutor方式的線程池、bean方法調用資訊追蹤。
@NewSpan是在tracer中建立一個span,@ContinueSpan是沿用本span。
追蹤資訊會異步傳輸到zipkin服務端儲存,直接在zipkin-ui中檢視追蹤資訊。