架構設計(12) --分佈式鏈路跟蹤:Zipkin實踐

最近升級改造我們鏈路跟蹤系統Log2,花了將近一週時間調研一些開源的鏈路跟蹤系統,在此調研過程中,做了一些筆記和總結分享出來,若有誤請指教。

分佈式鏈路跟蹤1: 理論知識

分佈式鏈路跟蹤2:Zipkin實踐

分佈式鏈路跟蹤3:skywalking原理和實踐

分佈式鏈路跟蹤4:自研組件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版本衝突,產生錯誤。

4)訪問http://localhost:9411/

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>

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章