乐趣区

关于分布式:分布式链路追踪系统-Zipkin-埋点库-Brave-使用入门

Zipkin 是什么

微服务架构下,服务之间的关系盘根错节。从调用一个 HTTP API 到最终返回后果,两头可能产生了多个服务间的调用。而这些被调用的服务,可能部署在不同的服务器上,由不同的团队开发,甚至可能应用了不同的编程语言。在这样的环境中,排查性能问题或者定位故障就很麻烦。

Zipkin 是一个分布式链路追踪零碎(distributed tracing system)。它能够收集并展现一个 HTTP 申请从开始到最终返回后果之间残缺的调用链。

基本概念

  • 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:

    • cs Client Send,客户端发动申请的,这是一个 span 的开始
    • sr Server Receive,服务端收到申请开始解决
    • ss Server Send,服务端解决申请实现并响应
    • cr Client Receive,客户端收到响应,这个 span 到此结束

    记录了以上的工夫点,就能够很容易剖析出一个 span 每个阶段的耗时:

    • cr - cs 是整个流程的耗时
    • sr - cs 以及 cr - ss 是网络耗时
    • ss - sr 是被调用服务解决业务逻辑的耗时

    然而,srss 两个 annotation 依赖被调用方,如果被调用方没有相应的记录,例如上游服务没有对接 instrumentation 库,或者像执行一条 SQL 这样的场景,被调用方是一个数据库服务,不会记录 srss,那么这个 span 就只有 cscr

相干文档:

  • https://zipkin.io/pages/instr…
  • 数据模型:https://zipkin.io/pages/data_…

B3 Propagation

当上游服务通过 HTTP 调用上游服务,如何将两个服务中的所有 span 串联起来,造成一个 trace,这就须要上游服务将 traceId 等信息传递给上游服务,而不能让上游从新生成一个 traceId。

Zipkin 通过 B3 流传标准(B3 Propagation),将相干信息(如 traceId、spanId 等)通过 HTTP 申请 Header 传递给上游服务:

   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 │ │
│ └───────────────────┘ │         └───────────────────┘         │ └───────────────────┘ │
│                       │                                       │                       │
└───────────────────────┘                                       └───────────────────────┘

相干文档:

  • https://github.com/openzipkin…

Brave 是什么

GitHub 仓库:https://github.com/openzipkin…

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)机制关联一些日志框架:

    • brave-context-slf4j SLF4J
    • brave-context-log4j2 Log4J 2
    • brave-context-log4j12 Log4J v1.2

    以 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-mysql6 和 brave-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 也是能够尝试一下其余的分布式链路追踪零碎。

  • Jaeger 是 Uber 开源的一套分布式追踪零碎,能够通过它的 9411 端口对接:https://www.jaegertracing.io/…
  • SkyWalking 提供了 Zipkin receiver 来接管 Zipkin 格局的数据:https://github.com/apache/sky…
  • 阿里云链路追踪 是阿里云提供的商业链路追踪零碎,对接文档:https://help.aliyun.com/docum…

关注我的公众号

退出移动版