1、Zipkin是什么
Zipkin分布式跟蹤系統;它可以幫助收集時間數據,解決在microservice架構下的延遲問題;它管理這些數據的收集和查找;Zipkin的設計是基於谷歌的Google Dapper論文。
每個應用程序向Zipkin報告定時數據,Zipkin UI呈現了一個依賴圖表來展示多少跟蹤請求經過了每個應用程序;如果想解決延遲問題,可以過濾或者排序所有的跟蹤請求,並且可以查看每個跟蹤請求占總跟蹤時間的百分比。
2、為什么使用Zipkin
隨着業務越來越復雜,系統也隨之進行各種拆分,特別是隨着微服務架構和容器技術的興起,看似簡單的一個應用,后台可能有幾十個甚至幾百個服務在支撐;一個前端的請求可能需要多次的服務調用最后才能完成;當請求變慢或者不可用時,我們無法得知是哪個后台服務引起的,這時就需要解決如何快速定位服務故障點,Zipkin分布式跟蹤系統就能很好的解決這樣的問題。
3、Zipkin下載和啟動
官方提供了三種方式來啟動,這里使用第二種方式來啟動;
curl -sSL https://zipkin.io/quickstart.sh | bash -s java -jar zipkin.jar
訪問localhost:9411
詳細參考:https://zipkin.io/pages/quick...
二、Zipkin架構
跟蹤器(Tracer)位於你的應用程序中,並記錄發生的操作的時間和元數據,提供了相應的類庫,對用戶的使用來說是透明的,收集的跟蹤數據稱為Span;
將數據發送到Zipkin的儀器化應用程序中的組件稱為Reporter,Reporter通過幾種傳輸方式之一將追蹤數據發送到Zipkin收集器(collector),
然后將跟蹤數據進行存儲(storage),由API查詢存儲以向UI提供數據。
架構圖如下:
1、Trace
Zipkin使用Trace結構表示對一次請求的跟蹤,一次請求可能由后台的若干服務負責處理,每個服務的處理是一個Span,Span之間有依賴關系,Trace就是樹結構的Span集合;
2、Span
每個服務的處理跟蹤是一個Span,可以理解為一個基本的工作單元,包含了一些描述信息:id,parentId,name,timestamp,duration,annotations等,例如:
{ "traceId": "bd7a977555f6b982", "name": "get-traces", "id": "ebf33e1a81dc6f71", "parentId": "bd7a977555f6b982", "timestamp": 1458702548478000, "duration": 354374, "annotations": [ { "endpoint": { "serviceName": "zipkin-query", "ipv4": "192.168.1.2", "port": 9411 }, "timestamp": 1458702548786000, "value": "cs" } ], "binaryAnnotations": [ { "key": "lc", "value": "JDBCSpanStore", "endpoint": { "serviceName": "zipkin-query", "ipv4": "192.168.1.2", "port": 9411 } } ] }
traceId:標記一次請求的跟蹤,相關的Spans都有相同的traceId;
id:span id;
name:span的名稱,一般是接口方法的名稱;
parentId:可選的id,當前Span的父Span id,通過parentId來保證Span之間的依賴關系,如果沒有parentId,表示當前Span為根Span;
timestamp:Span創建時的時間戳,使用的單位是微秒(而不是毫秒),所有時間戳都有錯誤,包括主機之間的時鍾偏差以及時間服務重新設置時鍾的可能性,
出於這個原因,Span應盡可能記錄其duration;
duration:持續時間使用的單位是微秒(而不是毫秒);
annotations:注釋用於及時記錄事件;有一組核心注釋用於定義RPC請求的開始和結束;
cs:Client Send,客戶端發起請求;
sr:Server Receive,服務器接受請求,開始處理;
ss:Server Send,服務器完成處理,給客戶端應答;
cr:Client Receive,客戶端接受應答從服務器;
binaryAnnotations:二進制注釋,旨在提供有關RPC的額外信息。
3、Transport
收集的Spans必須從被追蹤的服務運輸到Zipkin collector,有三個主要的傳輸方式:HTTP, Kafka和Scribe;
4、Components
有4個組件組成Zipkin:collector,storage,search,web UI
- collector:一旦跟蹤數據到達Zipkin collector守護進程,它將被驗證,存儲和索引,以供Zipkin收集器查找;
- storage:Zipkin最初數據存儲在Cassandra上,因為Cassandra是可擴展的,具有靈活的模式,並在Twitter中大量使用;但是這個組件可插入,除了Cassandra之外,還支持ElasticSearch和MySQL;
- search:一旦數據被存儲和索引,我們需要一種方法來提取它。查詢守護進程提供了一個簡單的JSON API來查找和檢索跟蹤,主要給Web UI使用;
- web UI:創建了一個GUI,為查看痕跡提供了一個很好的界面;Web UI提供了一種基於服務,時間和注釋查看跟蹤的方法。
三、Spring-boot中集成Zipkin示例
創建三個Springboot項目:service0,service1,service2
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>Springboot-Zipkin0</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</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>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--zipkin-brave start-->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-core</artifactId>
<version>3.9.0</version>
</dependency>
<!--http方式收集-->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spancollector-http</artifactId>
<version>3.9.0</version>
</dependency>
<!--servlet裝配-->
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-web-servlet-filter</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-apache-http-interceptors</artifactId>
<version>3.9.0</version>
</dependency>
<!--httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--zipkin-brave end-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
2、訪問zipkin工具類
package com.example.demo.bean; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.github.kristofa.brave.Brave; import com.github.kristofa.brave.Brave.Builder; import com.github.kristofa.brave.EmptySpanCollectorMetricsHandler; import com.github.kristofa.brave.Sampler; import com.github.kristofa.brave.SpanCollector; import com.github.kristofa.brave.http.DefaultSpanNameProvider; import com.github.kristofa.brave.http.HttpSpanCollector; import com.github.kristofa.brave.http.HttpSpanCollector.Config; import com.github.kristofa.brave.httpclient.BraveHttpRequestInterceptor; import com.github.kristofa.brave.httpclient.BraveHttpResponseInterceptor; import com.github.kristofa.brave.servlet.BraveServletFilter; @Configuration public class ZipkinBean { /** * 配置收集器 * * @return */ @Bean public SpanCollector spanCollector() { Config config = HttpSpanCollector.Config.builder().compressionEnabled(false).connectTimeout(5000) .flushInterval(1).readTimeout(6000).build(); return HttpSpanCollector.create("http://47.52.199.51:9411", config, new EmptySpanCollectorMetricsHandler()); } /** * Brave各工具類的封裝 * * @param spanCollector * @return */ @Bean public Brave brave(SpanCollector spanCollector) { Builder builder = new Builder("service2");// 指定serviceName builder.spanCollector(spanCollector); builder.traceSampler(Sampler.create(1));// 采集率 return builder.build(); } /** * 攔截器,需要serverRequestInterceptor,serverResponseInterceptor 分別完成sr和ss操作 * * @param brave * @return */ @Bean public BraveServletFilter braveServletFilter(Brave brave) { return new BraveServletFilter(brave.serverRequestInterceptor(), brave.serverResponseInterceptor(), new DefaultSpanNameProvider()); } /** * httpClient客戶端,需要clientRequestInterceptor,clientResponseInterceptor分別完成cs和cr操作 * * @param brave * @return */ @Bean public CloseableHttpClient httpClient(Brave brave) { CloseableHttpClient httpclient = HttpClients.custom() .addInterceptorFirst(new BraveHttpRequestInterceptor(brave.clientRequestInterceptor(), new DefaultSpanNameProvider())) .addInterceptorFirst(new BraveHttpResponseInterceptor(brave.clientResponseInterceptor())).build(); return httpclient; } }
3、controller代碼
service0
@RestController public class ZipkinController { @Autowired private CloseableHttpClient httpClient; @GetMapping("/service0") public String service() throws Exception { Thread.sleep(100); HttpGet get = new HttpGet("http://192.168.1.100:8081/service1"); CloseableHttpResponse response = httpClient.execute(get); return EntityUtils.toString(response.getEntity(), "utf-8"); } }
service1
@RestController public class ZipkinController { @Autowired private CloseableHttpClient httpClient; @GetMapping("/service1") public String service() throws Exception { Thread.sleep(100); HttpGet get = new HttpGet("http://192.168.1.100:8082/service2"); CloseableHttpResponse response = httpClient.execute(get); return EntityUtils.toString(response.getEntity(), "utf-8"); } }
service2
@RestController public class ZipkinController { @Autowired private CloseableHttpClient httpClient; @GetMapping("/service2") public String service() throws Exception { Thread.sleep(100); HttpGet get = new HttpGet("http://192.168.1.100:8082/hellow"); CloseableHttpResponse response = httpClient.execute(get); return EntityUtils.toString(response.getEntity(), "utf-8"); } }
4、啟動三個項目
訪問:http://192.168.1.100:8081/service1
5、打開zipkin地址
http://localhost:9411
四、zipkin UI界面詳解
1、首頁
首頁里面主要承載了trace的查詢功能,根據不同的條件,搜索出數據來
2、trace詳情
3、span詳情
這個圖中,需要注意的是相對時間和調用行為
調用行為分如下四種:
cs - Client Send : 客戶端已經提出了請求。這就設置了跨度的開始。
sr - Server Receive: 服務器已收到請求並將開始處理它。這與CS之間的差異將是網絡延遲和時鍾抖動的組合。
ss - Server Send: 服務器已完成處理,並將請求發送回客戶端。這與SR之間的差異將是服務器處理請求所花費的時間
cr - Client Receive : 客戶端已經收到來自服務器的響應。這就設置了跨度的終點。當記錄注釋時,RPC被認為是完整的。
相對時間:
表示在調用鏈開始到現在的時間,比如
從trace生成到現在,
17ms的時候,Client Send bas-ms這個應用發出了調用
19ms的時候,Server Receive ems-ms收到了bas-ms的調用。 這個說明,從bas-ms到ems-ms中間的網絡耗時花費了2ms.
34ms的時候,Server Send ems-ms的方法執行完畢,准備返回響應結果給bas-ms , 這說明ems-ms處理請求花費了34-19 = 15ms
34ms的時候,Client Receive bas-ms收到了返回結果
界面顯示的時候,是根據相對時間來排序的,所以Client Receive排在了第三位,因為他和Server Send的時間是一樣的。
4、全局依賴
點擊服務名,彈出如下框,顯示出了調用關系,
點擊具體的服務名,出現如下界面
Number of calls : 總的調用數(除去異常的)
Number of errors:調用異常的次數
五、源碼下載
github地址:https://github.com/Star-Lordxing/springboot-zipkin
本文主要參考:https://segmentfault.com/a/1190000012342007