調用鏈系列一、Zipkin架構介紹、Springboot集承(springmvc,HttpClient)調用鏈跟蹤、Zipkin UI詳解


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM