1. 程式人生 > >分散式鏈路呼叫監控系統——zipkin

分散式鏈路呼叫監控系統——zipkin

設想這麼一種情況,如果你的微服務數量逐漸增大,服務間的依賴關係越來越複雜,怎麼分析它們之間的呼叫關係及相互的影響?

服務追蹤分析

一個由微服務構成的應用系統通過服務來劃分問題域,通過REST請求服務API來連線服務來完成完整業務。對於入口的一個呼叫可能需要有多個後臺服務協同完成,鏈路上任何一個呼叫超時或出錯都可能造成前端請求的失敗。服務的呼叫鏈也會越來越長,並形成一個樹形的呼叫鏈。

隨著服務的增多,對呼叫鏈的分析也會越來越負責。設想你在負責下面這個系統,其中每個小點都是一個微服務,他們之間的呼叫關係形成了複雜的網路。

針對服務化應用全鏈路追蹤的問題,Google發表了

Dapper論文,介紹了他們如何進行服務追蹤分析。其基本思路是在服務呼叫的請求和響應中加入ID,標明上下游請求的關係。利用這些資訊,可以視覺化地分析服務呼叫鏈路和服務間的依賴關係。

對應Dapper的開源實現是Zipkin,支援多種語言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多種不同的庫來支援。



zipkin

zipkin為分散式鏈路呼叫監控系統,聚合各業務系統呼叫延遲資料,達到鏈路呼叫監控跟蹤。

architecture

slow service
如圖,在複雜的呼叫鏈路中假設存在一條呼叫鏈路響應緩慢,如何定位其中延遲高的服務呢?

  • 日誌: 通過分析呼叫鏈路上的每個服務日誌得到結果
  • zipkin:使用zipkinweb UI可以一眼看出延遲高的服務

zipkin

如圖所示,各業務系統在彼此呼叫時,將特定的跟蹤訊息傳遞至zipkin,zipkin在收集到跟蹤資訊後將其聚合處理、儲存、展示等,使用者可通過web UI方便 
獲得網路延遲、呼叫鏈路、系統依賴等等。

zipkin

zipkin主要涉及四個元件 collector storage search web UI

  • Collector接收各service傳輸的資料
  • Cassandra作為Storage的一種,也可以是mysql等,預設儲存在記憶體中,配置cassandra
    可以參考這裡
  • Query負責查詢Storage中儲存的資料,提供簡單的JSON API獲取資料,主要提供給web UI使用
  • Web 提供簡單的web介面

install

執行如下命令下載jar包

wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
  • 1

其為一個spring boot 工程,直接執行jar

nohup java -jar zipkin.jar & 
  • 1

terminology

使用zipkin涉及幾個概念

  • Span:基本工作單元,一次鏈路呼叫(可以是RPC,DB等沒有特定的限制)建立一個span,通過一個64位ID標識它, 
    span通過還有其他的資料,例如描述資訊,時間戳,key-value對的(Annotation)tag資訊,parent-id等,其中parent-id 
    可以表示span呼叫鏈路來源,通俗的理解span就是一次請求資訊

  • Trace:類似於樹結構的Span集合,表示一條呼叫鏈路,存在唯一標識

  • Annotation: 註解,用來記錄請求特定事件相關資訊(例如時間),通常包含四個註解資訊

    cs - Client Start,表示客戶端發起請求

    sr - Server Receive,表示服務端收到請求

    ss - Server Send,表示服務端完成處理,並將結果傳送給客戶端

    cr - Client Received,表示客戶端獲取到服務端返回資訊

  • BinaryAnnotation:提供一些額外資訊,一般已key-value對出現

概念說完,來看下完整的呼叫鏈路 
request chain

上圖表示一請求鏈路,一條鏈路通過Trace Id唯一標識,Span標識發起的請求資訊,各span通過parent id 關聯起來,如圖 
tree-like

整個鏈路的依賴關係如下: 
dependency

完成鏈路呼叫的記錄後,如何來計算呼叫的延遲呢,這就需要利用Annotation資訊

annotation

sr-cs 得到請求發出延遲

ss-sr 得到服務端處理延遲

cr-cs 得到真個鏈路完成延遲

brave

作為各呼叫鏈路,只需要負責將指定格式的資料傳送給zipkin即可,利用brave可快捷完成操作。

首先匯入jar包pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.6.RELEASE</version>
    </parent>



    <!-- https://mvnrepository.com/artifact/io.zipkin.brave/brave-core -->
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-core</artifactId>
            <version>3.9.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.zipkin.brave/brave-http -->
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-http</artifactId>
            <version>3.9.0</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.brave</groupId>
            <artifactId>brave-spancollector-http</artifactId>
            <version>3.9.0</version>
        </dependency>
        <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-okhttp</artifactId>
            <version>3.9.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.13</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.1</version>
        </dependency>

    </dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

利用spring boot建立工程

Application.java

package com.lkl.zipkin;

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

/**
 *
 * Created by liaokailin on 16/7/27.
 */
@SpringBootApplication
public class Application {


    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.run(args);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

建立controller對外提供服務

HomeController.java

RestController
@RequestMapping("/")
public class HomeController {

    @Autowired
    private OkHttpClient client;

    private  Random random = new Random();

    @RequestMapping("start")
    public String start() throws InterruptedException, IOException {
        int sleep= random.nextInt(100);
        TimeUnit.MILLISECONDS.sleep(sleep);
        Request request = new Request.Builder().url("http://localhost:9090/foo").get().build();
        Response response = client.newCall(request).execute();
        return " [service1 sleep " + sleep+" ms]" + response.body().toString();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

HomeController中利用OkHttpClient呼叫發起http請求。在每次發起請求時則需要通過brave記錄Span資訊,並非同步傳遞給zipkin 
作為被呼叫方(服務端)也同樣需要完成以上操作.

ZipkinConfig.java


package com.lkl.zipkin.config;

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

/**
 * Created by liaokailin on 16/7/27.
 */
@Configuration
public class ZipkinConfig {

    @Autowired
    private ZipkinProperties properties;


    @Bean
    public SpanCollector spanCollector() {
        HttpSpanCollector.Config config = HttpSpanCollector.Config.builder().connectTimeout(properties.getConnectTimeout()).readTimeout(properties.getReadTimeout())
                .compressionEnabled(properties.isCompressionEnabled()).flushInterval(properties.getFlushInterval()).build();
        return HttpSpanCollector.create(properties.getUrl(), config, new EmptySpanCollectorMetricsHandler());
    }


    @Bean
    public Brave brave(SpanCollector spanCollector){
        Brave.Builder builder = new Brave.Builder(properties.getServiceName());  //指定state
        builder.spanCollector(spanCollector);
        builder.traceSampler(Sampler.ALWAYS_SAMPLE);
        Brave brave = builder.build();
        return brave;
    }

    @Bean
    public BraveServletFilter braveServletFilter(Brave brave){
        BraveServletFilter filter = new BraveServletFilter(brave.serverRequestInterceptor(),brave.serverResponseInterceptor(),new DefaultSpanNameProvider());
        return filter;
    }

    @Bean
    public OkHttpClient okHttpClient(Brave brave){
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new BraveOkHttpRequestResponseInterceptor(brave.clientRequestInterceptor(), brave.clientResponseInterceptor(), new DefaultSpanNameProvider()))
                .build();
        return client;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • SpanCollector 配置收集器

  • Brave 各工具類的封裝,其中builder.traceSampler(Sampler.ALWAYS_SAMPLE)設定取樣比率,0-1之間的百分比

  • BraveServletFilter 作為攔截器,需要serverRequestInterceptor,serverResponseInterceptor 分別完成srss操作

  • OkHttpClient 新增攔截器,需要clientRequestInterceptor,clientResponseInterceptor 分別完成cscr操作,該功能由 
    brave中的brave-okhttp模組提供,同樣的道理如果需要記錄資料庫的延遲只要在資料庫操作前後完成cscr即可,當然brave提供其封裝。

以上還缺少一個配置資訊ZipkinProperties.java

package com.lkl.zipkin.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * Created by liaokailin on 16/7/28.
 */
@Configuration
@ConfigurationProperties(prefix = "com.zipkin")
public class ZipkinProperties {

    private String serviceName;

    private String url;

    private int connectTimeout;

    private int readTimeout;

    private int flushInterval;

    private boolean compressionEnabled;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public int getFlushInterval() {