1. 程式人生 > >SpringBoot整合Zipkin實現分散式全鏈路監控

SpringBoot整合Zipkin實現分散式全鏈路監控

目錄

  • Zipkin 簡介
  • Springboot 整合 Zipkin
    • 安裝啟動 zipkin
    • 版本說明
    • 專案結構
    • 工程埠分配
    • 引入 Maven 依賴
    • 配置檔案、收集器的設定
    • 編寫 Controller 傳送請求進行測試
    • Springboot 啟動類
    • 執行分析
  • 核心概念

Zipkin 簡介

Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in service architectures. Features include both the collection and lookup of this data.

If you have a trace ID in a log file, you can jump directly to it. Otherwise, you can query based on attributes such as service, operation name, tags and duration. Some interesting data will be summarized for you, such as the percentage of time spent in a service, and whether or not operations failed.

Application’s need to be “instrumented” to report trace data to Zipkin. This usually means configuration of a tracer or instrumentation library. The most popular ways to report data to Zipkin are via http or Kafka, though many other options exist, such as Apache ActiveMQ, gRPC and RabbitMQ. The data served to the UI is stored in-memory, or persistently with a supported backend such as Apache Cassandra or Elasticsearch.

Zipkin是一種分散式跟蹤系統。 它有助於收集解決服務體系結構中的延遲問題所需的計時資料。 功能包括收集和查詢此資料。

Zipkin是Twitter基於google的分散式監控系統Dapper(論文)的開發源實現,zipkin用於跟蹤分散式服務之間的應用資料鏈路,分析處理延時,幫助我們改進系統的效能和定位故障。Dapper論文地址

如果日誌檔案中有跟蹤ID,則可以直接跳轉到該檔案。 否則,你可以根據服務,操作名稱,tag標籤和持續時間等屬性進行查詢。 將為您總結一些有趣的資料,例如在服務中花費的時間佔比,以及操作是否失敗。

應用程式需要“檢測”以向Zipkin報告跟蹤資料。 這通常意味著配置跟蹤器或檢測庫。 向Zipkin報告資料的最常用方法是通過http或Kafka,儘管存在許多其他選項,例如Apache ActiveMQ,gRPC和RabbitMQ。 提供給UI的資料儲存在記憶體中,或者持久儲存在受支援的後端(如Apache Cassandra或Elasticsearch)中。

本示例中是使用Zipkin中整合的http元件進行傳送Span資料。

Springboot 整合 Zipkin

安裝啟動 zipkin

https://github.com/openzipkin/zipkin 中下載 zipkin.jar

java -jar zipkin.jar

版本說明

框架元件 Version
springboot 2.1.6.RELEASE
zipkin 3.9.0

專案結構

專案採用父工程整合多模組的方式構建而成,demo-zipkin 父工程聚合了zipkin-1zipkin-2zipkin-3zipkin-4zipkin-5 五個 Module。

demo-zipkin
    zipkin-1
        |-SpanCollectorConfig
        |-application.properties
        |-ZipkinController
        |-Application1
    zipkin-2
        |-SpanCollectorConfig
        |-application.properties
        |-ZipkinController2
        |-Application1
    zipkin-3
        |-SpanCollectorConfig
        |-application.properties
        |-ZipkinController3
        |-Application1
    zipkin-4
        |-SpanCollectorConfig
        |-application.properties
        |-ZipkinController4
        |-Application1
    zipkin-5
        |-SpanCollectorConfig
        |-application.properties
        |-ZipkinController5
        |-Application1

工程埠分配

每個 Module 使用不同的埠,分別啟動自己的Application。

Module名稱 Application
zipkin-1 8081 Application1
zipkin-2 8082 Application2
zipkin-3 8083 Application3
zipkin-4 8084 Application4
zipkin-5 8085 Application5

引入 Maven 依賴

<properties>
  <zipkin.version>3.9.0</zipkin.version>
</properties>

<!-- Springboot 相關 -->
<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相關 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-core</artifactId>
  <version>${zipkin.version}</version>
</dependency>
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-spancollector-http</artifactId>
  <version>${zipkin.version}</version>
</dependency>
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-web-servlet-filter</artifactId>
  <version>${zipkin.version}</version>
</dependency>
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-apache-http-interceptors</artifactId>
  <version>${zipkin.version}</version>
</dependency>
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-okhttp</artifactId>
  <version>${zipkin.version}</version>
</dependency>

配置檔案、收集器的設定

配置 application.properties,以 zipkin-1 為例,其他工程中配置時將 zipkin.serviceNameserver.port更改為 myzipkin-2 …8081...等即可

zipkin.serviceName=myzipkin-1
zipkin.url=http://localhost:9411
zipkin.connectTimeout=6000
zipkin.readTimeout=6000
zipkin.flushInterval=1
zipkin.compressionEnabled=true
zipkin.samplerRate=1

server.port=8081
server.servlet.context-path=/

配置Span收集器

設定收集器的詳細引數,包含超時時間、上傳span間隔、以及配置採集率等,進而對收集器進行初始化。

package com.anqi.zipkin.bean;

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.okhttp.BraveOkHttpRequestResponseInterceptor;
import com.github.kristofa.brave.servlet.BraveServletFilter;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpanCollectorConfig {

    @Value("${zipkin.url}")
    private String url;

    @Value("${zipkin.serviceName}")
    private String serviceName;

    /*
        連線超時時間
     */
    @Value("${zipkin.connectTimeout}")
    private int connecTimeout;

    /*
        是否啟動壓縮
     */
    @Value("${zipkin.compressionEnabled}")
    private boolean compressionEnabled;

    /*
        上傳 span 的間隔時間
     */
    @Value("${zipkin.flushInterval}")
    private int flushInterval;

    /*
        讀取超時時間
     */
    @Value("${zipkin.readTimeout}")
    private int readTimeout;

    @Value("${zipkin.samplerRate}")
    private float samplerRate;

    /**
     * 配置 span 收集器
     * @return
     */
    @Bean
    public SpanCollector spanCollector() {
        Config config = Config.builder()
                .connectTimeout(connecTimeout)
                .compressionEnabled(compressionEnabled)
                .flushInterval(flushInterval)
                .readTimeout(readTimeout)
                .build();

        return HttpSpanCollector.create(url, config, new EmptySpanCollectorMetricsHandler());
    }

    /**
     * 配置採集率
     * @param spanCollector
     * @return
     */
    @Bean
    public Brave brave(SpanCollector spanCollector) {
        Builder builder = new Builder(serviceName);
        builder.spanCollector(spanCollector)
                .traceSampler(Sampler.create(samplerRate))
                .build();
        return builder.build();
    }

    /**
     * @Description: 設定server的(服務端收到請求和服務端完成處理,並將結果傳送給客戶端)過濾器
     * @Param:
     * @return: 過濾器
     */
    @Bean
    public BraveServletFilter braveServletFilter(Brave brave) {
        BraveServletFilter filter = new BraveServletFilter(brave.serverRequestInterceptor(),
                brave.serverResponseInterceptor(), new DefaultSpanNameProvider());
        return filter;
    }

    /**
     * @Description: 設定client的 rs和cs的攔截器
     * @Param:
     * @return: OkHttpClient 返回請求例項
     */
    @Bean
    public OkHttpClient okHttpClient(Brave brave) {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new BraveOkHttpRequestResponseInterceptor(
                        brave.clientRequestInterceptor(),
                        brave.clientResponseInterceptor(),
                        new DefaultSpanNameProvider())).build();
        return httpClient;
    }
}

編寫 Controller 傳送請求進行測試

Zipkin-1中的Controller

package com.anqi.zipkin.controller;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("zipkin")
public class ZipkinController {

    public static final String url = "http://localhost:8082/zipkin/service2";

    @Autowired
    OkHttpClient client;


    @GetMapping("/service1")
    public String service() {
        Request request = new Request.Builder().url(url).build();
        Response response;
        try {
            response = client.newCall(request).execute();
            return response.body().string();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return "null";
    }
}

Zipkin-2中的Controller

package com.anqi.zipkin.controller;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("zipkin")
public class ZipkinController2 {
    public static final String url = "http://localhost:8083/zipkin/service3";
    public static final String url2 = "http://localhost:8084/zipkin/service4";

    @Autowired
    OkHttpClient client;

    @GetMapping("/service2")
    public String service() throws Exception {
        System.out.println("loading-----");
        Request request1 = new Request.Builder().url(url).build();
        Request request2 = new Request.Builder().url(url2).build();

        Response response1 = client.newCall(request1).execute();
        Response response2 = client.newCall(request2).execute();
        return "con2 + "+ response1.body().string() + "-" + response2.body().string();
    }
}

Zipkin-3中的Controller

package com.anqi.zipkin.controller;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("zipkin")
public class ZipkinController3 {
    public static final String url = "http://localhost:8084/zipkin/service4";

    @Autowired
    OkHttpClient client;

    @GetMapping("/service3")
    public String service() throws Exception {
        Request request = new Request.Builder().url(url).build();
        Response response = client.newCall(request).execute();
        return "con3 + "+ response.body().string();
    }
}

Zipkin-4中的Controller

package com.anqi.zipkin.controller;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("zipkin")
public class ZipkinController4 {
    public static final String url = "http://localhost:8085/zipkin/service5";

    @Autowired
    OkHttpClient client;

    @GetMapping("/service4")
    public String service() throws Exception {
        Request request = new Request.Builder().url(url).build();
        Response response = client.newCall(request).execute();
        return "con4 + "+ response.body().string();
    }
}

Zipkin-5中的Controller

package com.anqi.zipkin.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("zipkin")
public class ZipkinController5 {

    @GetMapping("/service5")
    public String service() throws Exception {

        return "service5 -----";
    }
}

Springboot 啟動類

package com.anqi.zipkin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application1 {
    public static void main(String[] args) {
        SpringApplication.run(Application1.class);
    }
}

執行分析

在位址列請求urlhttp://localhost:8081/zipkin/service1,然後訪問http://localhost:9411/zipkin/

檢視服務呼叫耗時

檢視服務依賴關係

核心概念

通過上圖可以瞭解到共有7個span,分別為

{
    "zipkin-1":["Server Start", "Server Finish"],
  "myzipkin-1,myzipkin-2":
        ["Client Start", "Server Start", "Client Finish", "Server Finish"],
    "myzipkin-2,myzipkin-3":
            ["Client Start", "Server Start", "Client Finish", "Server Finish"],
  "myzipkin-3,myzipkin-4":
            ["Client Start", "Server Start", "Client Finish", "Server Finish"],
    "myzipkin-4,myzipkin-5":
            ["Client Start", "Server Start", "Client Finish", "Server Finish"],
  "myzipkin-2,myzipkin-4":
            ["Client Start", "Server Start", "Client Finish", "Server Finish"],
    "myzipkin-4,myzipkin-5":
            ["Client Start", "Server Start", "Client Finish", "Server Finish"],
}

在json檔案中選取兩個子集進行分析

  • 基本資料:用於跟蹤樹中節點的關聯和介面展示,包括traceId、spanId、parentId、name、timestamp和duration,其中parentId為null的Span將成為跟蹤樹的根節點來展示,當然它也是呼叫鏈的起點,為了節省一次spanId的建立開銷,讓頂級Span變得更明顯,頂級Span中spanId將會和traceId相同。timestamp用於記錄呼叫的起始時間,而duration表示此次呼叫的總耗時,所以timestamp+duration將表示成呼叫的結束時間,而duration在跟蹤樹中將表示成該Span的時間條的長度。需要注意的是,這裡的name用於在跟蹤樹節點的時間條上展示。
  • traceId:標記一次請求的跟蹤,相關的Spans都有相同的traceId。
  • kind :zipkin最新V2版本的API中,不再要求在annotations中上傳cs,cr,sr,ss。而是通過kind標記是server-side span還是client-side span,兩端記錄自己的timestap來取代cs和sr,記錄duration來取代cr和ss
  {
    "traceId": "867a9e3867736b17",
    "parentId": "96f19423db38c90d",
    "id": "6c9fd521b6589b7f",
    "kind": "SERVER",
    "name": "get",
    "timestamp": 1568103422569000,
    "duration": 6000,
    "localEndpoint": {
      "serviceName": "myzipkin-4",
      "ipv4": "192.168.1.160"
    },
    "tags": {
      "http.status_code": "200",
      "http.url": "/zipkin/service4"
    }
  },
  {
    "traceId": "867a9e3867736b17",
    "parentId": "867a9e3867736b17",
    "id": "96f19423db38c90d",
    "kind": "CLIENT",
    "name": "get",
    "timestamp": 1568103422447000,
    "duration": 139000,
    "localEndpoint": {
      "serviceName": "myzipkin-1",
      "ipv4": "192.168.1.160"
    },
    "tags": {
      "http.url": "http://localhost:8082/zipkin/service2"
    }
  }