共计 10311 个字符,预计需要花费 26 分钟才能阅读完成。
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
是被调用服务解决业务逻辑的耗时
然而,
sr
和ss
两个 annotation 依赖被调用方,如果被调用方没有相应的记录,例如上游服务没有对接 instrumentation 库,或者像执行一条 SQL 这样的场景,被调用方是一个数据库服务,不会记录sr
和ss
,那么这个 span 就只有cs
和cr
。
相干文档:
- 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
来寄存TraceContext
。TraceContext
蕴含了一个 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…
关注我的公众号