天天看點

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

1. 簡介

Spring Cloud Sleuth為Spring Cloud實作了分布式跟蹤解決方案。

1.1 基本術語

Spring Cloud Sleuth借鑒了Dapper的術語。

Span:基本工作單元,例如,在一個建立的span中發送一個RPC等同于發送一個回應請求給RPC,span通過一個64位ID唯一辨別,trace以另一個64位ID表示,span還有其他資料資訊,比如摘要、時間戳事件、關鍵值注釋(tags)、span的ID、以及進度ID(通常是IP位址)

Spans在不斷的啟動和停止,同時記錄了時間資訊,當你建立了一個span,你必須在未來的某個時刻停止它。

Trace:一系列spans組成的一個樹狀結構,例如,如果你正在跑一個分布式大資料工程,你可能需要建立一個trace。

Annotation:用來及時記錄一個事件的存在,一些核心annotations用來定義一個請求的開始和結束

  • cs

    - Client Sent -用戶端發起一個請求,這個annotion描述了這個span的開始
  • sr

    - Server Received -服務端獲得請求并準備開始處理它,如果将其

    sr

    減去

    cs

    時間戳便可得到網絡延遲
  • ss

    - Server Sent -注解表明請求處理的完成(當請求傳回用戶端),如果

    ss

    減去

    sr

    時間戳便可得到服務端需要的處理請求時間
  • cr

    - Client Received -表明span的結束,用戶端成功接收到服務端的回複,如果

    cr

    減去cs時間戳便可得到用戶端從服務端擷取回複的所有所需時間

    下圖顯示了Span和Trace在一個系統中使用Zipkin注解的過程圖形化:

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

每種顔色表示一個span(有七個span-從A到G)。請考慮以下注意事項:

Trace Id = X
Span Id = D
Client Sent
           

該說明指出,目前span跟蹤編号設定為X和span辨別設定為d。此外,

Client Sent

事件發生了。

下圖顯示了跨度的父子關系

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

Zipkin 和 Config 結構類似,分為服務端 Server,用戶端 Client,用戶端就是各個微服務應用。

2. 搭建 Zipkin 服務端

在 Spring Boot 2.0 版本之後,官方已不推薦自己搭建定制了,而是直接提供了編譯好的 jar 包。詳情可以檢視官網:https://zipkin.io/pages/quickstart.html

本文介紹的還是自己搭建zipkin-server,原因本人項目上是使用的spring cloud 體系,想結合eureka注冊中心統一展示管理

2.1 建立 zipkin-server

引入依賴。pom檔案:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>spring-cloud-sleuth-zipkin-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <artifactId>server-zipkin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>server-zipkin</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除這個slf4j-log4j12-->
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入的zipkinServer依賴-->
        <!-- zipkin 服務類-->
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
            <version>2.12.2</version>
        </dependency>
        <!-- zipkin 界面-->
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
            <version>2.12.2</version>
        </dependency>
        <!-- 使用消息的方式收集資料(使用rabbitmq) -->
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-collector-rabbitmq</artifactId>
            <version>2.12.2</version>
        </dependency>
        <!-- zipkin 存儲到資料庫需要引入以下3個依賴 -->
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
            <version>2.12.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

2.2 配置檔案:

#配置服務及端口
server:
  port: 9411 #官方用的是9411,咱們也用這個
spring:
  main:
    allow-bean-definition-overriding: false #zipkin啟動報錯 解決The bean 'characterEncodingFilter', defined in class path resource [zipkin/autoconfigure/ui/ZipkinUiAutoConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration.class] and overriding is disabled.Action:
  application:
    name: server-zipkin
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/zipkin?characterEncoding=utf8&useSSL=true&verifyServerCertificate=false
    username: root
    password: 123456
management:
  metrics:
    web:
      server:
        auto-time-requests: false #zipkin啟動報錯 解決,Prometheus requires that all meters with the same name have the same set of tag keys. There is already an existing meter named 'http_server_requests_seconds' containing tag keys [exception, method, outcome, status, uri]. The meter you are attempting to register has keys [method, status, uri].
zipkin:
  collector:
    rabbitmq:
      addresses: 192.168.41.16:5672
      password: guest
      username: guest
      virtual-host: /
      queue: zipkin
  storage:
    type: mysql
           

2.3 啟動類:

@EnableZipkinServer
@SpringBootApplication
public class ServerZipkinApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerZipkinApplication.class, args);
    }

    @Bean
    @Primary
    public MySQLStorage mySQLStorage(DataSource datasource) {
        return MySQLStorage.newBuilder().datasource(datasource).executor(Runnable::run).build();
    }
}
           

這裡注入一個DataSource,需要在資料庫建幾張表

SQL語句參見:https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql

任一方式啟動後,通路 http://localhost:9411,可以看到服務端已經搭建成功

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

3 搭建 Zipkin 用戶端

建立兩個服務,service-hello、service-hi,service-hello 實作一個 REST 接口 /hello,/hello/hi,該接口裡調用/helloe/hi調用 service-hi 應用的接口。

3.1 建立 service-hello

3.2 引入依賴,pom 檔案:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>spring-cloud-sleuth-zipkin-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>service-hello</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-hello</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

3.3 配置檔案:

server:
  port: 8482
spring:
  application:
    name: service-hello
  rabbitmq:
    host: 192.168.41.16
    port: 5672
    username: guest
    password: guest
  zipkin:
    rabbitmq:
      queue: zipkin
  sleuth:
    sampler:
      #采樣率,推薦0.1,百分之百收集的話存儲可能扛不住
      probability: 1.0 #Sleuth 預設采樣算法的實作是 Reservoir sampling,具體的實作類是 PercentageBasedSampler,預設的采樣比例為: 0.1,即 10%。我們可以通過 spring.sleuth.sampler.probability 來設定,所設定的值介于 0 到 1 之間,1 則表示全部采集
           

3.4 啟動類

@SpringBootApplication
@RestController
public class ServiceHelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceHelloApplication.class, args);
    }

    private static final Logger LOG = Logger.getLogger(ServiceHelloApplication.class.getName());


    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String home(){
        LOG.log(Level.INFO, "hello is being called");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {

        }
        throw new RuntimeException("測試異常");
//        return "hi i'm service-hello!";
    }

    @RequestMapping(value = "/hello/hi", method = RequestMethod.GET)
    public String info(){
        LOG.log(Level.INFO, "hello/hi is being called");
        return restTemplate.getForObject("http://localhost:8483/hi",String.class);
    }

    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
           

4. 建立 service-hi

4.1 service-hi 的 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>spring-cloud-sleuth-zipkin-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <artifactId>service-hi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>service-hi</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入的zipkin依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
           

4.2 配置檔案如下:

server:
  port: 8482
spring:
  application:
    name: service-hi
  rabbitmq:
    host: 192.168.41.16
    port: 5672
    username: guest
    password: guest
  zipkin:
    rabbitmq:
      queue: zipkin
  sleuth:
    sampler:
      #采樣率,推薦0.1,百分之百收集的話存儲可能扛不住
      probability: 1.0 #Sleuth 預設采樣算法的實作是 Reservoir sampling,具體的實作類是 PercentageBasedSampler,預設的采樣比例為: 0.1,即 10%。我們可以通過 spring.sleuth.sampler.probability 來設定,所設定的值介于 0 到 1 之間,1 則表示全部采集
           

4.3 啟動類如下:

@SpringBootApplication
@RestController
public class ServiceHiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceHiApplication.class, args);
    }

    private static final Logger LOG = Logger.getLogger(ServiceHiApplication.class.getName());


    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

    @RequestMapping(value = "/hi", method = RequestMethod.GET)
    public String info() {
        LOG.log(Level.INFO, "calling trace service-hi ");
        return "i'm service-hi";
    }

    @RequestMapping(value = "/hi/hello", method = RequestMethod.GET)
    public String callHome() {
        LOG.log(Level.INFO, "calling trace service-hello  ");
        return restTemplate.getForObject("http://localhost:8481/hello", String.class);
    }
}
           

5. 驗證

依次啟動 zipkin-server、service-hello、service-hi,

通路 http://localhost:8481/hello/hi

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

通路 http://localhost:8482/hi/hello

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

6. 特性

接口通路已經成功,此時,我們檢視一下控制台的日志輸出:

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

從上面的控制台輸出内容中,我們可以看到多了一些如注意

[appname,traceId,spanId,exportable]

的日志資訊,而這些元素正是實作分布式服務跟蹤的重要組成部分,每個值的含義如下:

  • appname

    :service-hello,它記錄了應用的名稱
  • traceId

    :3c15adfc71e4da46,是 Spring Cloud Sleuth 生成的一個 ID,稱為 Trace ID,它用來辨別一

    請求鍊路。一條請求鍊路中包含一個 Trace ID,多個 Span ID。

  • spanId

    :266c1eb7011e3baf,是 Spring Cloud Sleuth 生成的另外一個 ID,稱為 Span ID,它表示一個基本的工作單元,比如發送一個 HTTP 請求。
  • exportable

    :true,它表示是否要将該資訊輸出到 Zipkin Server 中來收集和展示。

上面四個值中的 Trace ID 和 Span ID 是 Spring Cloud Sleuth 實作分布式服務跟蹤的核心。在一次請求中,會保持并傳遞同一個 Trace ID,進而将整個分布于不同微服務程序中的請求跟蹤資訊串聯起來。

下面我們通路 Zipkin Server 端,http://localhost:9411/

Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

有些小夥伴第一次進來發現服務名下并沒有看到我們的應用,這是為什麼呢?

這是因為 Spring Cloud Sleuth 采用了抽樣收集的方式來為跟蹤資訊打上收集标記,也就是上面看到的第四個值。為什麼要使用抽樣收集呢?理論上應該是收集的跟蹤資訊越多越好,可以更好的反映出系統的實際運作情況,但是在高并發的分布式系統運作時,大量請求調用會産生海量的跟蹤日志資訊,如果過多的收集,會對系統性能造成一定的影響,是以 Spring Cloud Sleuth 采用了抽樣收集的方式。

既然如此,那麼我們就需要把上面第四個值改為 true,開發過程中,我們一般都是收集全部資訊。

Sleuth 預設采樣算法的實作是 Reservoir sampling,具體的實作類是 PercentageBasedSampler,預設的采樣比例為: 0.1,即 10%。我們可以通過 spring.sleuth.sampler.probability 來設定,所設定的值介于 0 到 1 之間,1 則表示全部采集

本文是直接設定

spring.sleuth.sampler.probability=1.0

,采樣率100%

鍊路追蹤詳情
Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤
Spring Cloud Sleuth Zipkin + RabbitMQ + MySQL Demo 鍊路追蹤

7. demo源碼位址:

https://github.com/yanzhaoyao/spring-cloud-sleuth-zipkin-demo

具體的參考資料如下,推薦看spring cloud 官網的文檔,下面第一個

8. 參考資料:

https://cloud.spring.io/spring-cloud-sleuth/reference/html[推薦]

https://github.com/openzipkin/zipkin/tree/master/zipkin-server[推薦]

https://blog.csdn.net/hubo_88/article/details/80878632

https://blog.csdn.net/hubo_88/article/details/80889973

https://www.jianshu.com/p/4b9bf5a311fe

https://www.jianshu.com/p/4ea093c29c0e

https://www.cnblogs.com/lifeone/p/9040336.html

繼續閱讀