## 概述
一個(gè)完整的微服務(wù)系統(tǒng)包含多個(gè)微服務(wù)單元,,各個(gè)微服務(wù)子系統(tǒng)存在互相調(diào)用的情況,形成一個(gè) 調(diào)用鏈,。一個(gè)客戶(hù)端請(qǐng)求從發(fā)出到被響應(yīng) 經(jīng)歷了哪些組件,、哪些微服務(wù)、請(qǐng)求總時(shí)長(zhǎng),、每個(gè)組件所花時(shí)長(zhǎng) 等信息我們有必要了解和收集,,以幫助我們定位性能瓶頸、進(jìn)行性能調(diào)優(yōu),,因此監(jiān)控整個(gè)微服務(wù)架構(gòu)的調(diào)用鏈?zhǔn)钟斜匾?,本文將闡述如何使用 Zipkin 搭建微服務(wù)調(diào)用鏈追蹤中心。
Zipkin 初摸
正如 Ziplin 官網(wǎng) 所描述,,Zipkin 是一款分布式的追蹤系統(tǒng),,其可以幫助我們收集微服務(wù)架構(gòu)中用于解決延時(shí)問(wèn)題的時(shí)序數(shù)據(jù),,更直白地講就是可以幫我們追蹤調(diào)用的軌跡,。
Zipkin 的設(shè)計(jì)架構(gòu)如下圖所示:
要理解這張圖,需要了解一下 Zipkin 的幾個(gè)核心概念:
在某個(gè)應(yīng)用中安插的用于發(fā)送數(shù)據(jù)給 Zipkin 的組件稱(chēng)為 Report,,目的就是用于追蹤數(shù)據(jù)收集
微服務(wù)中調(diào)用一個(gè)組件時(shí),,從發(fā)出請(qǐng)求開(kāi)始到被響應(yīng)的過(guò)程會(huì)持續(xù)一段時(shí)間,,將這段跨度稱(chēng)為 Span
從 Client 發(fā)出請(qǐng)求到完成請(qǐng)求處理,中間會(huì)經(jīng)歷一個(gè)調(diào)用鏈,,將這一個(gè)整個(gè)過(guò)程稱(chēng)為一個(gè)追蹤( Trace ),。一個(gè) Trace 可能包含多個(gè) Span,,反之每個(gè) Span 都有一個(gè)上級(jí)的 Trace。
一種數(shù)據(jù)傳輸?shù)姆绞?,比如最?jiǎn)單的 HTTP 方式,,當(dāng)然在高并發(fā)時(shí)可以換成 Kafka 等消息隊(duì)列
看了一下基本概念后,再結(jié)合上面的架構(gòu)圖,,可以試著理解一下,,只有裝配有 Report 組件的 Client 才能通過(guò) Transport 來(lái)向 Zipkin 發(fā)送追蹤數(shù)據(jù)。追蹤數(shù)據(jù)由 Collector 收集器進(jìn)行手機(jī)然后持久化到 Storage 之中,。最后需要數(shù)據(jù)的一方,,可以通過(guò) UI 界面調(diào)用 API 接口,從而最終取到 Storage 中的數(shù)據(jù),??梢?jiàn)整體流程不復(fù)雜。
Zipkin 官網(wǎng)給出了各種常見(jiàn)語(yǔ)言支持的 OpenZipkin libraries:
本文接下來(lái)將 構(gòu)造微服務(wù)追蹤的實(shí)驗(yàn)場(chǎng)景 并使用 Brave 來(lái)輔助完成微服務(wù)調(diào)用鏈追蹤中心搭建,!
部署 Zipkin 服務(wù)
利用 Docker 來(lái)部署 Zipkin 服務(wù)再簡(jiǎn)單不過(guò)了:
docker run -d -p 9411:9411 --name zipkin docker.io/openzipkin/zipkin
完成之后瀏覽器打開(kāi):localhost:9411 可以看到 Zipkin 的可視化界面:
模擬微服務(wù)調(diào)用鏈
我們來(lái)構(gòu)造一個(gè)如下圖所示的調(diào)用鏈:
圖中包含 一個(gè)客戶(hù)端 + 三個(gè)微服務(wù):
-
Client:使用 /servicea 接口消費(fèi) ServiceA 提供的服務(wù)
-
ServiceA:使用 /serviceb 接口消費(fèi) ServiceB 提供的服務(wù),,端口 8881
-
ServiceB:使用 /servicec 接口消費(fèi) ServiceC 提供的服務(wù),端口 8882
-
ServiceC:提供終極服務(wù),,端口 8883
為了模擬明顯的延時(shí)效果,,準(zhǔn)備在每個(gè)接口的響應(yīng)中用代碼加入 3s 的延時(shí)。
簡(jiǎn)單起見(jiàn),,我們用 SpringBt 來(lái)實(shí)現(xiàn)三個(gè)微服務(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 !”;
}
}
我們將三個(gè)微服務(wù)都啟動(dòng)起來(lái),然后瀏覽器中輸入localhost:8881/servicea 來(lái)發(fā)出請(qǐng)求,,過(guò)了 9s 之后,,將取到 ServiceC 中提供的微服務(wù)接口所返回的內(nèi)容,如下圖所示:
很明顯,,調(diào)用鏈可以正常 work 了,!
那么接下來(lái)我們就要引入 Zipkin 來(lái)追蹤這個(gè)調(diào)用鏈的信息!
編寫(xiě)與 Zipkin 通信的工具組件
從 Zipkin 官網(wǎng)我們可以知道,,借助 OpenZipkin 庫(kù) Brave,,我們可以開(kāi)發(fā)一個(gè)封裝 Brave 的公共組件,讓其能十分方便地嵌入到 ServiceA,,ServiceB,,ServiceC 服務(wù)之中,完成與 Zipkin 的通信,。
為此我們需要建立一個(gè)新的基于 Maven 的 Java 項(xiàng)目: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>
- 編寫(xiě) ZipkinProperties 類(lèi)
其包含 endpoint 和 service 兩個(gè)屬性,,我們最后是需要將該兩個(gè)參數(shù)提供給 ServiceA、ServiceB,、ServiceC 微服務(wù)作為其 application.properties 中的 Zipkin 配置
@Data
@Component
@ConfigurationProperties("zipkin")
public class ZipkinProperties {
private String endpoint;
private String service;
}
用了 lombok 之后,,這個(gè)類(lèi)異常簡(jiǎn)單,!
[注意:關(guān)于 lombok 的用法,可以看這里]
- 編寫(xiě) ZipkinConfiguration 類(lèi)
這個(gè)類(lèi)很重要,,在里面我們將 Brave 的 BraveClientHttpRequestInterceptor 攔截器注冊(cè)到 RestTemplate 的攔截器調(diào)用鏈中來(lái)收集請(qǐng)求數(shù)據(jù)到 Zipkin 中,;同時(shí)還將 Brave 的 ServletHandlerInterceptor 攔截器注冊(cè)到調(diào)用鏈中來(lái)收集響應(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 三個(gè) SpringBt 項(xiàng)目的 application.properties 中加入 Zipkin 的配置:
以 ServiceA 為例:
server.port=8881
zipkin.endpoint=http://你 Zipkin 服務(wù)所在機(jī)器的 IP:9411/api/v1/spans
zipkin.service=servicea
我們最后依次啟動(dòng) ServiceA、ServiceB,、和 ServiceC 三個(gè)微服務(wù),并開(kāi)始實(shí)驗(yàn)來(lái)收集鏈路追蹤數(shù)據(jù) ,!
## 實(shí)際實(shí)驗(yàn)
1. 依賴(lài)分析
瀏覽器打開(kāi) Zipkin 的 UI 界面,,可以查看 依賴(lài)分析:
圖中十分清晰地展示了 ServiceA、ServiceB 和 ServiceC 三個(gè)服務(wù)之間的調(diào)用關(guān)系,!
注意,,該圖可縮放,并且每一個(gè)元素均可以點(diǎn)擊,,例如點(diǎn)擊 ServiceB 這個(gè)微服務(wù),,可以看到其調(diào)用鏈的上下游!
2. 查找調(diào)用鏈
接下來(lái)我們看一下調(diào)用鏈相關(guān),,點(diǎn)擊 服務(wù)名,,可以看到 Zipkin 監(jiān)控到個(gè)所有服務(wù):
同時(shí)可以查看 Span,如以 ServiceA 為例,,其所有 REST 接口都再下拉列表中:
以 ServiceA 為例,,點(diǎn)擊 Find Traces,可以看到其所有追蹤信息:
點(diǎn)擊某個(gè)具體 Trace,,還能看到詳細(xì)的每個(gè) Span 的信息,,如下圖中,可以看到 A B C 調(diào)用過(guò)程中每個(gè) REST 接口的詳細(xì)時(shí)間戳:
點(diǎn)擊某一個(gè) REST 接口進(jìn)去還能看到更詳細(xì)的信息,,如查看 /servicec 這個(gè) REST 接口,,可以看到從發(fā)送請(qǐng)求到收到響應(yīng)信息的所有詳細(xì)步驟:
參考文獻(xiàn)
|