分佈式鏈路追蹤系統 Zipkin 埋點庫 Brave 使用入門

Zipkin 是什麼

微服務架構下,服務之間的關係錯綜複雜。從調用一個 HTTP API 到最終返回結果,中間可能發生了多個服務間的調用。而這些被調用的服務,可能部署在不一樣的服務器上,由不一樣的團隊開發,甚至可能使用了不一樣的編程語言。在這樣的環境中,排查性能問題或者定位故障就很麻煩。html

Zipkin 是一個分佈式鏈路追蹤系統(distributed tracing system)。它能夠收集並展現一個 HTTP 請求從開始到最終返回結果之間完整的調用鏈。java

基本概念

  • Trace 表明一個完整的調用鏈。一個 trace 對應一個隨機生成的惟一的 traceId。例如一個 HTTP 請求到響應是一個 trace。一個 trace 內部包含多個 span。
  • Span Trace 中的一個基本單元。一個 span 一樣對應一個隨機生成的惟一的 spanId。例如一個 HTTP 請求到響應過程當中,內部可能會訪問型數據庫執行一條 SQL,這是一個新的 span,或者內部調用另一個服務的 HTTP API 也是一個新的 span。一個 trace 中的全部 span 是一個樹形結構,樹的根節點叫作 root span。除 root span 外,其餘 span 都會包含一個 parentId,表示父級 span 的 spanId。
  • Annotation 每一個 span 中包含多個 annotation,用來記錄關鍵事件的時間點。例如一個對外的 HTTP 請求從開始到結束,依次有如下幾個 annotation:mysql

    • cs Client Send,客戶端發起請求的,這是一個 span 的開始
    • sr Server Receive,服務端收到請求開始處理
    • ss Server Send,服務端處理請求完成並響應
    • cr Client Receive,客戶端收到響應,這個 span 到此結束

    記錄了以上的時間點,就能夠很容易分析出一個 span 每一個階段的耗時:git

    • cr - cs 是整個流程的耗時
    • sr - cs 以及 cr - ss 是網絡耗時
    • ss - sr 是被調用服務處理業務邏輯的耗時

    然而,srss 兩個 annotation 依賴被調用方,若是被調用方沒有相應的記錄,例以下游服務沒有對接 instrumentation 庫,或者像執行一條 SQL 這樣的場景,被調用方是一個數據庫服務,不會記錄 srss,那麼這個 span 就只有 cscrgithub

相關文檔:web

B3 Propagation

當上遊服務經過 HTTP 調用下游服務,如何將兩個服務中的全部 span 串聯起來,造成一個 trace,這就須要上游服務將 traceId 等信息傳遞給下游服務,而不能讓下游從新生成一個 traceId。spring

Zipkin 經過 B3 傳播規範(B3 Propagation),將相關信息(如 traceId、spanId 等)經過 HTTP 請求 Header 傳遞給下游服務:sql

Client Tracer                                                  Server Tracer     
┌───────────────────────┐                                       ┌───────────────────────┐
│                       │                                       │                       │
│   TraceContext        │          Http Request Headers         │   TraceContext        │
│ ┌───────────────────┐ │         ┌───────────────────┐         │ ┌───────────────────┐ │
│ │ TraceId           │ │         │ X-B3-TraceId      │         │ │ TraceId           │ │
│ │                   │ │         │                   │         │ │                   │ │
│ │ ParentSpanId      │ │ Inject  │ X-B3-ParentSpanId │ Extract │ │ ParentSpanId      │ │
│ │                   ├─┼────────>│                   ├─────────┼>│                   │ │
│ │ SpanId            │ │         │ X-B3-SpanId       │         │ │ SpanId            │ │
│ │                   │ │         │                   │         │ │                   │ │
│ │ Sampling decision │ │         │ X-B3-Sampled      │         │ │ Sampling decision │ │
│ └───────────────────┘ │         └───────────────────┘         │ └───────────────────┘ │
│                       │                                       │                       │
└───────────────────────┘                                       └───────────────────────┘

相關文檔:數據庫

Brave 是什麼

GitHub 倉庫: https://github.com/openzipkin...apache

Brave is a distributed tracing instrumentation library.

翻譯: Brave 是分佈式鏈路追蹤的埋點庫。

instrumentation 這個單詞本意是"儀器、儀表、器曲譜寫",爲了更加便於理解,這裏我翻譯爲"埋點"。埋點的意思就是在程序的關鍵位置(即上面介紹的各個 annotation)作一些記錄。

在 GitHub 倉庫的 instrumentation 目錄中,能夠看到官方已經提供了很是多的 instrumentation。

另外在 https://zipkin.io/pages/trace... 文檔中,還有其餘非 Java 語言的 instrumentation 以及非官方提供的 instrumentation,能夠根據須要來選擇。其餘 instrumentation 本文不作介紹,本文重點是 Zipkin 官方提供的 Java 語言 instrumentation : Brave 。

Spring MVC 項目配置 Brave

本文以 Web 服務爲例,不涉及像 Dubbo 這樣的 RPC 服務。

假設現有一個 Spring MVC 項目想要對接 Zipkin,須要使用 Brave 埋點,並將相關數據提交到 Zipkin 服務上。

Maven 依賴管理

首先加入一個 dependencyManagement,這樣就不須要在各個依賴包中添加版本號了:

<dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.zipkin.brave</groupId>
        <artifactId>brave-bom</artifactId>
        <version>5.11.2</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
</dependencyManagement>

最新版本號能夠在這裏查看:
https://mvnrepository.com/art...

須要注意的是,不一樣版本配置方法會略有差別,具體能夠參考官方文檔。本文使用的 Brave 版本號爲 5.11.2

建立 Tracing 對象

添加依賴:

<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-context-slf4j</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-sender-okhttp3</artifactId>
</dependency>

下面提供了兩種配置方式(Java 配置方式XML 配置方式)建立 Tracing 對象,須要根據項目的實際狀況選擇其中一種。

Java 配置方式

若是現有的項目是 Spring Boot 項目或者非 XML 配置的 Spring 項目,能夠採用這種方式。

@Configuration
public class TracingConfiguration {

    @Bean
    public Tracing tracing() {
        Sender sender = OkHttpSender.create("http://127.0.0.1:9411/api/v2/spans");
        Reporter<Span> spanReporter = AsyncReporter.create(sender);

        Tracing tracing = Tracing.newBuilder()
                .localServiceName("my-service")
                .spanReporter(spanReporter)
                .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
                        .addScopeDecorator(MDCScopeDecorator.get()).build())
                .build();
        return tracing;
    }
}

XML 配置方式

若是現有項目是採用 XML 配置的 Spring 項目,能夠採用這種方式。

相對於 Java 配置方式,須要多添加一個 brave-spring-beans 依賴:

<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-spring-beans</artifactId>
</dependency>

該模塊提供了一系列 Spring FactoryBean,用於經過 XML 來建立對象:

<bean id="sender" class="zipkin2.reporter.beans.OkHttpSenderFactoryBean">
    <property name="endpoint" value="http://localhost:9411/api/v2/spans"/>
</bean>

<bean id="correlationScopeDecorator" class="brave.spring.beans.CorrelationScopeDecoratorFactoryBean">
    <property name="builder">
        <bean class="brave.context.slf4j.MDCScopeDecorator" factory-method="newBuilder"/>
    </property>
</bean>

<bean id="tracing" class="brave.spring.beans.TracingFactoryBean">
    <property name="localServiceName" value="my-service"/>
    <property name="spanReporter">
        <bean class="zipkin2.reporter.beans.AsyncReporterFactoryBean">
            <property name="sender" ref="sender"/>
        </bean>
    </property>
    <property name="currentTraceContext">
        <bean class="brave.spring.beans.CurrentTraceContextFactoryBean">
            <property name="scopeDecorators" ref="correlationScopeDecorator"/>
        </bean>
    </property>
</bean>

代碼分析

上面兩種方式本質上是同樣的,都是建立了一個 Tracing 對象。

該對象是單實例的,若是想要在其餘地方獲取到這個對象,能夠經過靜態方法 Tracing tracing = Tracing.current() 來獲取。

Tracing 對象提供了一系列 instrumentation 所須要的工具,例如 tracing.tracer() 能夠獲取到 Tracer 對象,Tracer 對象的做用後面會有詳細介紹。

建立 Tracing 對象一些相關屬性:

  • localServiceName 服務的名稱
  • spanReporter 指定一個 Reporter<zipkin2.Span> 對象做爲埋點數據的提交方式,這裏一般會使用靜態方法 AsyncReporter.create(Sender sender) 來建立一個 AsyncReporter 對象,固然若是有特殊需求也能夠本身實現 Reporter 接口來自定義提交方式。建立 AsyncReporter 對象須要提供一個 Sender,下面列出了一些官方提供的 Sender 可供選擇:

    • zipkin-sender-okhttp3 使用 OkHttp3 提交,使用方法:sender = OkHttpSender.create("http://localhost:9411/api/v2/spans"),本文中的示例使用的就是這種方式
    • zipkin-sender-urlconnection 使用 Java 自帶的 java.net.HttpURLConnection 提交,使用方法:sender = URLConnectionSender.create("http://localhost:9411/api/v2/spans")
    • zipkin-sender-activemq-client 使用 ActiveMQ 消息隊列提交,使用方法:sender = ActiveMQSender.create("failover:tcp://localhost:61616")
    • zipkin-sender-kafka 使用 Kafka 消息隊列提交,使用方法:sender = KafkaSender.create("localhost:9092")
    • zipkin-sender-amqp-client 使用 RabbitMQ 消息隊列提交,使用方法:sender = RabbitMQSender.create("localhost:5672")
  • currentTraceContext 指定一個 CurrentTraceContext 對象來設置 TraceContext 對象的做用範圍,一般會使用 ThreadLocalCurrentTraceContext,也就是用 ThreadLocal 來存放 TraceContextTraceContext 包含了一個 trace 的相關信息,例如 traceId。

    因爲在 Spring MVC 應用中,一個請求的業務邏輯一般在同一個線程中(暫不考慮異步 Servlet)。一個請求內部的全部業務邏輯應該共用一個 traceId,天然是把 TraceContext 放在 ThreadLocal 中比較合理。這也意味着,默認狀況下 traceId 只在當前線程有效,跨線程會失效。固然,跨線程也有對應的方案,本文後續會有詳細介紹。

  • CurrentTraceContext 中能夠添加 ScopeDecorator ,經過 MDC (Mapped Diagnostic Contexts) 機制關聯一些日誌框架:

    以 Logback 爲例(本文中案例使用的方式),能夠配置下面的 pattern 在日誌中輸出 traceId 和 spanId:

    <pattern>%d [%X{traceId}/%X{spanId}] [%thread] %-5level %logger{36} - %msg%n</pattern>

Spring MVC 埋點

添加依賴:

<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-spring-webmvc</artifactId>
</dependency>

建立 HttpTracin 對象

首先建立 HttpTracing 對象,用於 HTTP 協議鏈路追蹤。

Java 配置方式:

@Bean
public HttpTracing httpTracing(Tracing tracing){
    return HttpTracing.create(tracing);
}

XML 配置方式:

<bean id="httpTracing" class="brave.spring.beans.HttpTracingFactoryBean">
    <property name="tracing" ref="tracing"/>
</bean>

添加 DelegatingTracingFilter

DelegatingTracingFilter 用於處理外部調用的 HTTP 請求,記錄 sr(Server Receive) 和 ss(Server Send) 兩個 annotation。

非 Spring Boot 項目能夠在 web.xml 中添加 DelegatingTracingFilter

<filter>
  <filter-name>tracingFilter</filter-name>
  <filter-class>brave.spring.webmvc.DelegatingTracingFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>tracingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

若是是 Spring Boot 項目能夠用 FilterRegistrationBean 來添加 DelegatingTracingFilter

@Bean
public FilterRegistrationBean delegatingTracingFilterRegistrationBean() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new DelegatingTracingFilter());
    registration.setName("tracingFilter");
    return registration;
}

若是有興趣的話能夠看下 DelegatingTracingFilter源碼,它本質上是一個 TracingFilter 的代理。TracingFilter 來源於 brave-instrumentation-servlet 模塊。DelegatingTracingFilter 經過 Spring 容器中的 HttpTracing 對象建立了一個 TracingFilter。相關代碼在 DelegatingTracingFilter.java 54 行

到此,Spring MVC 項目已經完成了最基本的 Brave 埋點和提交 Zipkin 的配置。若是有現有的 Zipkin 服務,將建立 OkHttpSender 提供的接口地址換成實際地址,啓動服務後經過 HTTP 請求一下服務,就會在 Zipkin 上找到一個對應的 trace。

其餘 instrumentation 介紹

因爲每一個服務內部還會調用其餘服務,例如經過 HTTP 調用外部服務的 Api、鏈接遠程數據庫執行 SQL,此時還須要用到其餘 instrumentation。

因爲篇幅有限,下面僅介紹幾個經常使用的 instrumentation。

brave-instrumentation-mysql

brave-instrumentation-mysql 能夠爲 MySQL 上執行的每條 SQL 語句生成一個 span,用於分析 SQL 的執行時間。

添加依賴:

<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-mysql</artifactId>
</dependency>

使用方法:在 JDBC 鏈接地址末尾加上參數 ?statementInterceptors=brave.mysql.TracingStatementInterceptor 便可。

該模塊用於 mysql-connector-java 5.x 版本,另外還有 brave-instrumentation-mysql6brave-instrumentation-mysql8 可分別用於 mysql-connector-java 6+ 和 mysql-connector-java 8+ 版本。

brave-instrumentation-okhttp3

brave-instrumentation-okhttp3 用於 OkHttp 3.x,在經過 OkHttpClient 請求外部 API 時,生成 span,而且經過 B3 傳播規範將鏈路信息傳遞給被調用方。

添加依賴:

<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-instrumentation-okhttp3</artifactId>
</dependency>

使用方法:

OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .dispatcher(new Dispatcher(
                        httpTracing.tracing().currentTraceContext()
                                .executorService(new Dispatcher().executorService())
                ))
                .addNetworkInterceptor(TracingInterceptor.create(httpTracing))
                .build();

若是你使用的 HTTP 客戶端庫不是 OkHttp 而是 Apache HttpClient 的話,可使用 brave-instrumentation-httpclient

更多玩法

獲取當前 traceId 和 spanId

Span currentSpan = Tracing.currentTracer().currentSpan(); // 獲取當前 span
if (currentSpan != null) {
    String traceId = currentSpan.context().traceIdString();
    String spanId = currentSpan.context().spanIdString();
}

自定義 tag

可將業務相關的信息寫入 tag 中,方便在查看調用鏈信息時關聯查看業務相關信息。

Span currentSpan = Tracing.currentTracer().currentSpan(); // 獲取當前 span
if (currentSpan != null) {
    currentSpan.tag("biz.k1", "v1").tag("biz.k2", "v2");
}

建立新 span

若是使用了某些組件訪問外部服務,找不到官方或開源的 instrumentation,或者有一個本地的耗時任務,也想經過建立一個 span 來記錄任務的運行時間和結果,能夠本身建立一個新的 span。

ScopedSpan span = Tracing.currentTracer().startScopedSpan("span name");
try {
    // 訪問外部服務 或 本地耗時任務
} catch (Exception e) {
    span.error(e); // 任務出錯
    throw e;
} finally {
    span.finish(); // 必須記得結束 span
}

下面是另一種方式,這種方式提供了更多的特性:

Tracer tracer = Tracing.currentTracer();
Span span = tracer.nextSpan().name("span name").start();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) { // SpanInScope 對象須要關閉
    // 訪問外部服務 或 本地耗時任務
} catch (Exception e) {
    span.error(e); // 任務出錯
    throw e;
} finally {
    span.finish(); // 必須記得結束 span
}

跨線程追蹤

使用包裝過的 Runnable 和 Callable 對象

Runnable runnable = ...; // 原始的 Runnable 對象
Runnable tracingRunnable = Tracing.current().currentTraceContext().wrap(runnable); // 包裝過的 Runnable 對象

一樣的方式也可使用於 Callable 對象。

使用包裝過的線程池

ExecutorService service = ....;
ExecutorService proxiedService = tracing.currentTraceContext().executorService(service);

對接除 Zipkin 外的其餘分佈式追蹤系統

除 Zipkin 以外,還有不少優秀的開源或商業的分佈式鏈路追蹤系統。其中一部分對 Zipkin 協議作了兼容,若是不想使用 Zipkin 也是能夠嘗試一下其餘的分佈式鏈路追蹤系統。

關注個人公衆號

掃碼關注

相關文章
相關標籤/搜索