最近升級改造我們鏈路跟蹤系統Log2,花了將近一週時間調研一些開源的鏈路跟蹤系統,在此調研過程中,做了一些筆記和總結分享出來,若有誤請指教。
一、Zipkin是什麼
《架構設計(12) 分佈式鏈路跟蹤》提到,Google的Dapper論文,介紹瞭如何進行服務追蹤分析。其基本思路是在服務調用的請求和響應中加入ID,標明上下游請求的關係。利用這些信息,可以可視化地分析服務調用鏈路和服務間的依賴關係。
Zipkin是基於 Dapper 論文實現,由Twitter公司開發並開源一個分佈式的跟蹤系統。Zipkin支持多種語言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多種不同的庫來支持。
官網:https://github.com/openzipkin/zipkin/
二、Zipkin基本概念
1、trace:請求跟蹤樹
Zipkin使用Trace結構表示一次請求的跟蹤,從請求到達跟蹤系統的邊界開始,到被跟蹤系統返回響應爲止的過程。一次請求可能由後臺的若干服務負責處理,每個服務的處理是一個Span,Span之間有依賴關係,Trace就是樹結構的Span集合;.包含一系列的span,它們組成了一個樹型結構.
2、span: 服務跟蹤記錄
每個 trace中會調用若干個服務,爲了記錄調用了哪些服務,以及每次調用的消耗時間等信息,在每次調用服務時,埋入一個調用記錄,稱爲一個“span”。
span是基本的工作單元,包含了一些描述信息:id,parentId,name,timestamp,duration,annotations等
最開始的初始Span稱爲根span,此span中span id和 trace id值相同。
3、annotations標註:標註時間點發生的Event
標註用於及時記錄事件;有一組核心註釋用於定義RPC請求的開始和結束;
- cs:Client Send,客戶端發起請求;
- sr:Server Receive,服務器接受請求,開始處理;
- ss:Server Send,服務器完成處理,給客戶端應答;
- cr:Client Receive,客戶端接受應答從服務器;
4、binaryAnnotations額外信息
旨在提供有關RPC的額外信息,可以存放用戶自定義信息,比如:sessionID、userID、userIP、異常等。
trace信息例子
一個完成trace信息如下:
traceId:標記一次請求的跟蹤,相關的Spans都有相同的traceId;
id:span id;
name:span的名稱,一般是接口方法的名稱;
parentId:可選的id,當前Span的父Span id,通過parentId來保證Span之間的依賴關係,如果沒有parentId,表示當前Span爲根Span;
timestamp:Span創建時的時間戳,使用的單位是微秒(而不是毫秒),所有時間戳都有錯誤,包括主機之間的時鐘偏差以及時間服務重新設置時鐘的可能性,出於這個原因,Span應儘可能記錄其duration;
duration:持續時間使用的單位是微秒(而不是毫秒);
{
"traceId": "423f62ed10f826bd",
"id": "09d324b8d0563b0c",
"name": "http:/hello",
"parentId": "423f62ed10f826bd",
"timestamp": 1593490361270000,
"duration": 19000,
"annotations": [
{
"timestamp": 1593490361270000,
"value": "cs",
"endpoint": {
"serviceName": "zipkin-client",
"port": 8080
}
},
{
"timestamp": 1593490361278000,
"value": "sr",
"endpoint": {
"serviceName": "zipkin-service",
"port": 9081
}
},
{
"timestamp": 1593490361289000,
"value": "cr",
"endpoint": {
"serviceName": "zipkin-client",
"port": 8080
}
},
{
"timestamp": 1593490361290000,
"value": "ss",
"endpoint": {
"serviceName": "zipkin-service",
"port": 9081
}
}
],
"binaryAnnotations": [
{
"key": "http.host",
"value": "localhost",
"endpoint": {
"serviceName": "zipkin-client",
"port": 8080
}
},
{
"key": "http.method",
"value": "GET",
"endpoint": {
"serviceName": "zipkin-client",
"port": 8080
}
}
]
}
三、Zipkin架構和原理
1、Zipkin 架構
跟蹤器(Tracer):位於你的應用程序中,並記錄發生的操作的時間和元數據,提供了相應的類庫,對用戶的使用來說是透明的,收集的跟蹤數據稱爲Span;
報告器 (Reporter):將跟蹤數據發送到Zipkin server的應用程序組件稱爲Reporter. 比如通過MQ發送,即MQ爲reporter,或者跟蹤數據先落地,通過logstash發送,logstash爲reporter。
Transport傳輸:Reporter通過幾種傳輸方式之一將追蹤數據發送到Zipkin收集器(collector),有三個主要的傳輸方式:HTTP, Kafka和Scribe;
Zipkin server:collector收到數據後將跟蹤數據進行存儲(storage),由API查詢存儲以向UI提供數據。
Zipkin server有4個組件組成:collector,storage,search,web UI
collector:一旦跟蹤數據到達Zipkin collector守護進程,它將被驗證,存儲和索引,以供Zipkin收集器查找;
storage:Zipkin最初數據存儲在Cassandra上,因爲Cassandra是可擴展的,具有靈活的模式,並在Twitter中大量使用;但是這個組件可插入,除了Cassandra之外,還支持ElasticSearch和MySQL; 存儲,zipkin默認的存儲方式爲in-memory,即不會進行持久化操作。如果想進行收集數據的持久化,可以存儲數據在Cassandra,因爲Cassandra是可擴展的,有一個靈活的模式,並且在Twitter中被大量使用,我們使這個組件可插入。除了Cassandra,我們原生支持ElasticSearch和MySQL。其他後端可能作爲第三方擴展提供。
search:一旦數據被存儲和索引,我們需要一種方法來提取它。查詢守護進程提供了一個簡單的JSON API來查找和檢索跟蹤,主要給Web UI使用;
web UI:創建了一個GUI,爲查看痕跡提供了一個很好的界面;Web UI提供了一種基於服務,時間和註釋查看跟蹤的方法。
架構圖如下:
2、Zipkin工作流程:
一個應用的代碼發起HTTP get請求,經過Trace框架攔截,然後
1)、把當前調用鏈的Trace信息添加到HTTP Header裏面
2)、記錄當前調用的時間戳
3)、發送HTTP請求,把trace相關的header信息攜帶上
4)、調用結束之後,記錄當前調用耗時
5)、然後把上面流程產生的 信息彙集成一個span,把這個span信息上傳到zipkin的Collector模塊
該信息來源:https://www.jianshu.com/p/d75226c8f943
┌─────────────┐ ┌───────────────────────┐ ┌─────────────┐ ┌──────────────────┐
│ User Code │ │ Trace Instrumentation │ │ Http Client │ │ Zipkin Collector │
└─────────────┘ └───────────────────────┘ └─────────────┘ └──────────────────┘
│ │ │ │
┌─────────┐
│ ──┤GET /foo ├─▶ │ ────┐ │ │
└─────────┘ │ record tags
│ │ ◀───┘ │ │
────┐
│ │ │ add trace headers │ │
◀───┘
│ │ ────┐ │ │
│ record timestamp
│ │ ◀───┘ │ │
┌─────────────────┐
│ │ ──┤GET /foo ├─▶ │ │
│X-B3-TraceId: aa │ ────┐
│ │ │X-B3-SpanId: 6b │ │ │ │
└─────────────────┘ │ invoke
│ │ │ │ request │
│
│ │ │ │ │
┌────────┐ ◀───┘
│ │ ◀─────┤200 OK ├─────── │ │
────┐ └────────┘
│ │ │ record duration │ │
┌────────┐ ◀───┘
│ ◀──┤200 OK ├── │ │ │
└────────┘ ┌────────────────────────────────┐
│ │ ──┤ asynchronously report span ├────▶ │
│ │
│{ │
│ "traceId": "aa", │
│ "id": "6b", │
│ "name": "get", │
│ "timestamp": 1483945573944000,│
│ "duration": 386000, │
│ "annotations": [ │
│--snip-- │
└────────────────────────────────┘
四、JAVA相關採集插件。
1、brave則是zipkin官方出品的Java語言的鏈路數據採集插件:官方提供的brave插件列表非常多,基本上涵蓋了日常用到的鏈路:http、rpc、db等。
2、spring cloud提供了spring-cloud-sleuth-zipkin來方便集成zipkin實現zipkin client追蹤器,該jar包可以通過spring-cloud-starter-zipkin依賴來引入。Spring Cloud Sleuth是採集應用Span、Trace等信息,通過HTTP Request,向Zipkin Server發送採集信息等全部自動完成。
五、zipkin結合spring-cloud-sleuth使用
我們新建三個項目。一個zipkinServer。另外兩個是普通的業務應用,分別叫service和client。
zipkinServer功能SHI 收集調用數據,並展示;
service對外暴露接口;
client : 調用service接口;
1、zipkin server應用
Zipkin的使用比較簡單,官網有說明幾種方式:
1、容器 :Docker Zipkin
項目能夠建立docker
鏡像,提供腳本和docker-compose.yml
來啓動預構建的圖像。最快的開始是直接運行最新鏡像:
docker run -d -p 9411:9411 openzipkin/zipkin
2、下載jar :最快的方法是下載一個zipkinserver.xx.jar並運行,詳情參看
3、使用源碼代碼運行
1)、新建Spring Boot項目,工程取名爲zipkin-server,在其pom引入依賴:
spring boot 版本:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
spirng cloud 版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
我們使用spirng cloud 版本:
<?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>
<!-- spring boot -->
<!--<parent>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-parent</artifactId>-->
<!--<version>1.4.3.RELEASE</version>-->
<!--</parent>-->
<groupId>com.demo.zipkin</groupId>
<artifactId>demo-zipkin-server</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-zipkin-server</name>
<description>zipkin-server project for Spring Boot</description>
<properties>
<!--設置字符編碼及java版本 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<log4j2.version>2.7</log4j2.version>
</properties>
<!--依賴管理,用於管理spring-cloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--增加zipkin的依賴 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)、在其程序入口類, 加上註解@EnableZipkinServer,開啓ZipkinServer的功能:
package com.demo.zipkin.web;
/**
* Created by huangguisu on 2019/10/28.
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import zipkin.server.EnableZipkinServer;
@SpringBootApplication
@EnableZipkinServer
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class ZipkinServerApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(ZipkinServerApplication.class, args);
}
}
3)、在配置文件application.yml指定,配置Zipkin服務端口、名稱等:
server.port=9411
spring.application.name=demo-zipkin-server
如果啓動報錯:
Exception in thread "main" java.lang.AbstractMethodError: org.springframework.boot.context.config.ConfigFileApplicationListener.supportsSourceType(Ljava/lang/Class;)Z
去掉工程裏面的pom.xml文
parent
依賴spring-boot-starter-parent
,因爲已經使用dependencyManagement配置,<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
如果再添加上
parent
依賴的話就會造成spring boot版本衝突,產生錯誤。
2、zipkin clint和service
1)、增加依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2)、client的application.properties設置zipkin server
server.port=8080
server.address=0.0.0.0
spring.application.name=zipkin-client
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1
3)client啓動代碼:
package com.demo.zipkin.web;
/**
* Created by huangguisu on 2019/10/28.
*/
import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.client.RestTemplate;
import org.springframework.context.annotation.Bean;
@RestController
@SpringBootApplication
public class ClientApplication {
// @Autowired
// RestTemplate restTemplate;
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
@GetMapping("/hello")
public String hello(){
return this.restTemplate().getForEntity("http://localhost:9081/hello",String.class).getBody();
}
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
4)service的application.properties設置zipkin server
server.port=9081
server.address=0.0.0.0
spring.application.name=zipkin-client
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1
5)service啓動代碼:
package com.demo.zipkin.web;
/**
* Created by huangguisu on 2019/10/28.
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@SpringBootApplication
@RestController
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class SerivceApplication {
@GetMapping("/hello")
public String hello(){
return "Hello World";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SerivceApplication.class, args);
}
}
然後我們啓動client和service後,訪問:http://localhost:8080/hello
3、查看依賴:
可以看到如下依賴關係:
spring-cloud-sleuth收集信息是有一定的比率的,默認的採樣率是0.1.可能在剛剛啓動時候,看不到數據。
配置此值的方式在配置文件中增加spring.sleuth.sampler.percentage參數配置(如果不配置默認0.1),如果我們調大此值爲1,可以看到信息收集就更及時。
如果spring.sleuth.sampler.percentage是1,由於採樣頻率過高,如果一次請求的鏈路有5個,那就需要5次http請求zipkin server,這種方式追蹤服務調用鏈路會給我們業務程序性能帶來一定的影響。
#sleuth採樣率,默認爲0.1,值越大收集越及時,但性能影響也越大
spring.sleuth.sampler.percentage=1
span細節指標展示:
使用默認zipkin方式的問題:
問題1:zipkin客戶端向zipkin-server程序發送數據使用的是http的方式通信,每次發送的時候涉及到連接和發送過程。
問題2:當我們的zipkin-server程序關閉或者重啓過程中,因爲客戶端收集信息的發送採用http的方式會被丟失。
針對以上兩個問題,可以改進的辦法是:
1、通信採用socket或者其他效率更高的通信方式。
2、客戶端數據的發送儘量減少業務線程的時間消耗,採用異步等方式發送收集信息。
3、客戶端與zipkin-server之間增加緩存類的中間件,例如redis、MQ等,在zipkin-server程序掛掉或重啓過程中,客戶端依舊可以正常的發送自己收集的信息。
相信採用以上三種方式會很大的提高我們的效率和可靠性。其實spring-cloud已經爲我們提供採用MQ或redis等其他的採用socket方式通信,利用消息中間件或數據庫緩存的實現方式。
4、spring-cloud-sleuth配置說明
這些配置跟版本有關,僅作參考:
spring.zipkin.base-url: http://192.168.84.146:9411/ #指定zipkin的服務器
#默認的service name是讀取spring.application.name的值.
spring.zipkin.service.name=service1
#支持通過服務發現定位主機名
spring.zipkin.locator.discovery.enabled: true
#支持壓縮的能力。默認是false。開啓壓縮,這樣在發送給zipkin server之前會先把數據進行壓縮
spring.zipkin.compression.enabled=true
#spring2.0以上 0.1-1.0 1=100%即集服務的全部追蹤數據
spring.zipkin.sleuth.sampler.probability: 1.0
#spring2.0以下 0.1-1.0 1=100%即採集服務的全部追蹤數據
spring.zipkin.sleuth.percentage: 1
spring.zipkin.sleuth.stream.enabled: true
#Spring Cloud Sleuth本身就整合了Spring Integration。它發佈/訂閱事件都是會創建span。可以設置spring.sleuth.integration.enabled=false來禁用這個機制
spring.zipkin.sleuth.stream.integration.enabled: false
#如果不需要跟蹤某些@Scheduled,可以在spring.sleuth.scheduled.skipPattern設置一些正則表達式來過濾一些class。
spring.zipkin.sleuth.scheduled.skip-pattern: "^(org.*HystrixStreamTask|com.*ServiceAuthUtil*|com.*DBAuthClientService)$"
#web開啓sleuth功能
spring.zipkin.sleuth.web.client.enabled: true
#uri在這個裏面配置了,那麼就不會上報到zipkin, 每個uri中間 |分割
spring.zipkin.sleuth.web.client.skip-pattern: "/hystrix.stream"
六、dubbo使用brave
1、添加brave依賴
<!-- dubbo插件 -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-instrumentation-dubbo-rpc</artifactid>
</dependency>
2、設置brave dubbo filter
brave dubbo filter添加到dubbo的filter鏈中:
- 方法1:在application.properties文件中添加兩行配置:
dubbo.consumer.filter=tracing
dubbo.provider.filter=tracing
- 方法2:在dubbo xml配置文件中添加配置
<dubbo:consumer filter="tracing" />
<dubbo:provider filter="tracing" />
7、brave相關依賴包
使用到的brave依賴
<!-- 核心依賴 -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave</artifactid>
</dependency>
<!-- reporter -->
<dependency>
<groupid>io.zipkin.reporter2</groupid>
<artifactid>zipkin-sender-okhttp3</artifactid>
</dependency>
<dependency>
<groupid>io.zipkin.reporter2</groupid>
<artifactid>zipkin-sender-kafka</artifactid>
</dependency>
<!-- 日誌依賴 -->
<!-- Integrates so you can use log patterns like %X{traceId}/%X{spanId} -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-context-slf4j</artifactid>
</dependency>
<!-- spring mvc項目支持 -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-spring-beans</artifactid>
</dependency>
<!-- mvc插件 -->
<!-- Adds the MVC class and method names to server spans -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-instrumentation-spring-webmvc</artifactid>
</dependency>
<!-- httpclient插件 -->
<!-- Instruments the underlying HttpClient requests that call the backend -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-instrumentation-httpclient</artifactid>
</dependency>
<!-- dubbo插件 -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-instrumentation-dubbo-rpc</artifactid>
</dependency>
<!-- mysql插件 -->
<dependency>
<groupid>io.zipkin.brave</groupid>
<artifactid>brave-instrumentation-mysql8</artifactid>
</dependency>