## 概述
一個完整的微服務(wù)系統(tǒng)包含多個微服務(wù)單元,各個微服務(wù)子系統(tǒng)存在互相調(diào)用的情況,形成一個 調(diào)用鏈。一個客戶端請求從發(fā)出到被響應(yīng) 經(jīng)歷了哪些組件、哪些微服務(wù)、請求總時長、每個組件所花時長 等信息我們有必要了解和收集,以幫助我們定位性能瓶頸、進行性能調(diào)優(yōu),因此監(jiān)控整個微服務(wù)架構(gòu)的調(diào)用鏈?zhǔn)钟斜匾?,本文將闡述如何使用 Zipkin 搭建微服務(wù)調(diào)用鏈追蹤中心。
Zipkin 初摸
正如 Ziplin 官網(wǎng) 所描述,Zipkin 是一款分布式的追蹤系統(tǒng),其可以幫助我們收集微服務(wù)架構(gòu)中用于解決延時問題的時序數(shù)據(jù),更直白地講就是可以幫我們追蹤調(diào)用的軌跡。
Zipkin 的設(shè)計架構(gòu)如下圖所示:

要理解這張圖,需要了解一下 Zipkin 的幾個核心概念:
在某個應(yīng)用中安插的用于發(fā)送數(shù)據(jù)給 Zipkin 的組件稱為 Report,目的就是用于追蹤數(shù)據(jù)收集
微服務(wù)中調(diào)用一個組件時,從發(fā)出請求開始到被響應(yīng)的過程會持續(xù)一段時間,將這段跨度稱為 Span
從 Client 發(fā)出請求到完成請求處理,中間會經(jīng)歷一個調(diào)用鏈,將這一個整個過程稱為一個追蹤( Trace )。一個 Trace 可能包含多個 Span,反之每個 Span 都有一個上級的 Trace。
一種數(shù)據(jù)傳輸?shù)姆绞剑热缱詈唵蔚?HTTP 方式,當(dāng)然在高并發(fā)時可以換成 Kafka 等消息隊列
看了一下基本概念后,再結(jié)合上面的架構(gòu)圖,可以試著理解一下,只有裝配有 Report 組件的 Client 才能通過 Transport 來向 Zipkin 發(fā)送追蹤數(shù)據(jù)。追蹤數(shù)據(jù)由 Collector 收集器進行手機然后持久化到 Storage 之中。最后需要數(shù)據(jù)的一方,可以通過 UI 界面調(diào)用 API 接口,從而最終取到 Storage 中的數(shù)據(jù)??梢娬w流程不復(fù)雜。
Zipkin 官網(wǎng)給出了各種常見語言支持的 OpenZipkin libraries:

本文接下來將 構(gòu)造微服務(wù)追蹤的實驗場景 并使用 Brave 來輔助完成微服務(wù)調(diào)用鏈追蹤中心搭建!
部署 Zipkin 服務(wù)
利用 Docker 來部署 Zipkin 服務(wù)再簡單不過了:
docker run -d -p 9411:9411 --name zipkin docker.io/openzipkin/zipkin
完成之后瀏覽器打開:localhost:9411可以看到 Zipkin 的可視化界面:

模擬微服務(wù)調(diào)用鏈
我們來構(gòu)造一個如下圖所示的調(diào)用鏈:

圖中包含 一個客戶端 + 三個微服務(wù):
-
Client:使用 /servicea 接口消費 ServiceA 提供的服務(wù)
-
ServiceA:使用 /serviceb 接口消費 ServiceB 提供的服務(wù),端口 8881
-
ServiceB:使用 /servicec 接口消費 ServiceC 提供的服務(wù),端口 8882
-
ServiceC:提供終極服務(wù),端口 8883
為了模擬明顯的延時效果,準(zhǔn)備在每個接口的響應(yīng)中用代碼加入 3s 的延時。
簡單起見,我們用 SpringBt 來實現(xiàn)三個微服務(wù)。
ServiceA 的控制器代碼如下:
@RestController
public class ServiceAContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/servicea ”)
public String servicea() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.getForObject("http://localhost:8882/serviceb", String.class);
}
}
ServiceB 的代碼如下:
@RestController
public class ServiceBContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/serviceb ”)
public String serviceb() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return restTemplate.getForObject("http://localhost:8883/servicec", String.class);
}
}
ServiceC 的代碼如下:
@RestController
public class ServiceCContorller {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/servicec ”)
public String servicec() {
try {
Thread.sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Now, we reach the terminal call: servicec !”;
}
}
我們將三個微服務(wù)都啟動起來,然后瀏覽器中輸入localhost:8881/servicea來發(fā)出請求,過了 9s 之后,將取到 ServiceC 中提供的微服務(wù)接口所返回的內(nèi)容,如下圖所示:

很明顯,調(diào)用鏈可以正常 work 了!
那么接下來我們就要引入 Zipkin 來追蹤這個調(diào)用鏈的信息!
編寫與 Zipkin 通信的工具組件
從 Zipkin 官網(wǎng)我們可以知道,借助 OpenZipkin 庫 Brave,我們可以開發(fā)一個封裝 Brave 的公共組件,讓其能十分方便地嵌入到 ServiceA,ServiceB,ServiceC 服務(wù)之中,完成與 Zipkin 的通信。
為此我們需要建立一個新的基于 Maven 的 Java 項目:ZipkinTool
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven./POM/4.0.0"
xmlns:xsi="http://www./2001/XMLSchema-instance"
xsi:schemaLocation="http://maven./POM/4.0.0 http://maven./xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hansonwang99</groupId>
<artifactId>ZipkinTool</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.1.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.7.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-web-servlet-interceptor</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-resttemplate-interceptors</artifactId>
<version>4.0.6</version>
</dependency>
<dependency>
<groupId>io.zipkin.reporter</groupId>
<artifactId>zipkin-sender-okhttp3</artifactId>
<version>0.6.12</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
其包含 endpoint 和 service 兩個屬性,我們最后是需要將該兩個參數(shù)提供給 ServiceA、ServiceB、ServiceC 微服務(wù)作為其 application.properties 中的 Zipkin 配置
@Data
@Component
@ConfigurationProperties("zipkin")
public class ZipkinProperties {
private String endpoint;
private String service;
}
用了 lombok 之后,這個類異常簡單!
[注意:關(guān)于 lombok 的用法,可以看這里]
這個類很重要,在里面我們將 Brave 的 BraveClientHttpRequestInterceptor 攔截器注冊到 RestTemplate 的攔截器調(diào)用鏈中來收集請求數(shù)據(jù)到 Zipkin 中;同時還將 Brave 的 ServletHandlerInterceptor 攔截器注冊到調(diào)用鏈中來收集響應(yīng)數(shù)據(jù)到 Zipkin 中
上代碼吧:
@Configuration
@Import({RestTemplate.class, BraveClientHttpRequestInterceptor.class, ServletHandlerInterceptor.class})
public class ZipkinConfiguration extends WebMvcConfigurerAdapter {
@Autowired
private ZipkinProperties zipkinProperties;
@Autowired
private RestTemplate restTemplate;
@Autowired
private BraveClientHttpRequestInterceptor clientInterceptor;
@Autowired
private ServletHandlerInterceptor serverInterceptor;
@Bean
public Sender sender() {
return OkHttpSender.create( zipkinProperties.getEndpoint() );
}
@Bean
public Reporter<Span> reporter() {
return AsyncReporter.builder(sender()).build();
}
@Bean
public Brave brave() {
return new Brave.Builder(zipkinProperties.getService()).reporter(reporter()).build();
}
@Bean
public SpanNameProvider spanNameProvider() {
return new SpanNameProvider() {
@Override
public String spanName(HttpRequest httpRequest) {
return String.format(
"%s %s",
httpRequest.getHttpMethod(),
httpRequest.getUri().getPath()
);
}
};
}
@PostConstruct
public void init() {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(clientInterceptor);
restTemplate.setInterceptors(interceptors);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(serverInterceptor);
}
}
ZipkinTool 完成以后,我們需要在 ServiceA、ServiceB、ServiceC 三個 SpringBt 項目的 application.properties 中加入 Zipkin 的配置:
以 ServiceA 為例:
server.port=8881
zipkin.endpoint=http://你 Zipkin 服務(wù)所在機器的 IP:9411/api/v1/spans
zipkin.service=servicea
我們最后依次啟動 ServiceA、ServiceB、和 ServiceC 三個微服務(wù),并開始實驗來收集鏈路追蹤數(shù)據(jù) !
## 實際實驗
1. 依賴分析
瀏覽器打開 Zipkin 的 UI 界面,可以查看 依賴分析:

圖中十分清晰地展示了 ServiceA、ServiceB 和 ServiceC 三個服務(wù)之間的調(diào)用關(guān)系!
注意,該圖可縮放,并且每一個元素均可以點擊,例如點擊 ServiceB 這個微服務(wù),可以看到其調(diào)用鏈的上下游!

2. 查找調(diào)用鏈
接下來我們看一下調(diào)用鏈相關(guān),點擊 服務(wù)名,可以看到 Zipkin 監(jiān)控到個所有服務(wù):

同時可以查看 Span,如以 ServiceA 為例,其所有 REST 接口都再下拉列表中:

以 ServiceA 為例,點擊 Find Traces,可以看到其所有追蹤信息:
點擊某個具體 Trace,還能看到詳細(xì)的每個 Span 的信息,如下圖中,可以看到 A B C 調(diào)用過程中每個 REST 接口的詳細(xì)時間戳:

點擊某一個 REST 接口進去還能看到更詳細(xì)的信息,如查看 /servicec 這個 REST 接口,可以看到從發(fā)送請求到收到響應(yīng)信息的所有詳細(xì)步驟:

參考文獻
|