在这个背景下,Google 揭橥了 Dapper 的论文,描述了如何通过一个分布式追踪系统办理上述问题。基于该论文,各大互联网公司实现并支配了自己的分布式追踪系统,个中比较出名的有阿里巴巴的 EagleEye。本文中提到的 Zipkin 是 Twitter 公司开源的分布式追踪系统。下面会详细先容如何在 Dubbo 中利用 Zipkin 来实现分布式追踪。
Zipkin 简介
Zipkin 是基于 Dapper 论文实现,由 Twitter 开源的分布式追踪系统,通过网络分布式做事实行韶光的信息来达到追踪做事调用链路、以及剖析做事实行延迟等目的。

Zipkin 架构
Collector 网络器、Storage 存储、API、UI 用户界面等几部分构成了 Zipkin Server 部分,对应于 GitHub 上 openzipkin/zipkin 这个项目。而网络运用中调用的耗时信息并将其上报的组件与运用共生,并拥有各个措辞的实现版本,个中 Java 的实现是 GitHub 上 openzipkin/brave。除了 Java 客户端实现之外,openzipkin 还供应了许多其他措辞的实现,个中包括了 go、php、JavaScript、.net、ruby 等,详细列表可以参阅 Zipkin 的 Exiting instrumentations。
Zipkin 的事情过程
当用户发起一次调用时,Zipkin 的客户端会在入口处为整条调用链路天生一个全局唯一的 trace id,并为这条链路中的每一次分布式调用天生一个 span id。span 与 span 之间可以有父子嵌套关系,代表分布式调用中的高下游关系。span 和 span 之间可以是兄弟关系,代表当前调用下的两次子调用。一个 trace 由一组 span 组成,可以算作是由 trace 为根节点,span 为多少个子节点的一棵树。
Span 由调用边界来分隔,在 Zipkin 中,调用边界由以下四个 annotation 来表示:
cs - Clent Sent 客户端发送了要求sr - Server Receive 做事端接管到要求ss - Server Send 做事端处理完毕,向客户端发送回应cr - Client Receive 客户端收到结果显然,通过这四个 annotation 上的韶光戳,可以轻易的知道一次完全的调用在不同阶段的耗时,比如:
sr - cs 代表了要求在网络上的耗时ss - sr 代表了做事端处理要求的耗时cr - ss 代表了回应在网络上的耗时cr - cs 代表了一次调用的整体耗时Zipkin 会将 trace 干系的信息在调用链路上通报,并在每个调用边界结束时异步的把当前调用的耗时信息上报给 Zipkin Server。Zipkin Server 在收到 trace 信息后,将其存储起来,Zipkin 支持的存储类型有 inMemory、MySql、Cassandra、以及 ElasticsSearch 几种办法。随后 Zipkin 的 Web UI 会通过 API 访问的办法从存储中将 trace 信息提取出来剖析并展示,如下图所示:
在 Dubbo 中利用
由于 Brave 对 Dubbo 已经主动做了支持,在 Dubbo 中集成基于 Zipkin 的链路追踪变的十分大略。下面会按照 Brave 中关于 Dubbo RPC 支持的指引来解释如何在 Dubbo 中利用 Zipkin。
安装 Zipkin Server
按照 Zipkin 官方文档中的快速开始 来安装 Zipkin,如下所示:
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s$ java -jar zipkin.jar
按照这种办法安装的 Zipkin Server 利用的存储类型是 inMemory 的。当做事器停机之后,所有网络到的 trace 信息会丢失,不适用于生产系统。如果在生产系统中利用,须要配置其余的存储类型。Zipkin 支持 MySql、Cassandra、和 ElasticSearch。推举利用 Cassandra 和 ElasticSearch,干系的配置请自行查阅官方文档。
本文为了演示方便,利用的存储是 inMemory 类型。成功启动之后,可以在终端看到如下的提示:
$ java -jar zipkin.jarPicked up JAVA_TOOL_OPTIONS: -Djava.awt.headless=true :: Powered by Spring Boot :: (v2.0.5.RELEASE)...o.s.b.w.e.u.UndertowServletWebServer : Undertow started on port(s) 9411 (http) with context path ''2018-10-10 18:40:31.605 INFO 21072 --- [ main] z.s.ZipkinServer : Started ZipkinServer in 6.835 seconds (JVM running for 8.35)
然后在浏览器中访问 http://localhost:9411 验证 WEB 界面。
配置 Maven 依赖
引入 Brave 依赖
新建一个新的 Java 工程,并在 pom.xml 中引入 Brave 干系的依赖如下:
<properties> <brave.version>5.4.2</brave.version> <zipkin-reporter.version>2.7.9</zipkin-reporter.version> </properties> <dependencyManagement> <dependencies> <!-- 引入 zipkin brave 的 BOM 文件 --> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-bom</artifactId> <version>${brave.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 引入 zipkin repoter 的 BOM 文件 --> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-bom</artifactId> <version>${zipkin-reporter.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 1. brave 对 dubbo 的集成 --> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-instrumentation-dubbo-rpc</artifactId> </dependency> <!-- 2. brave 的 spring bean 支持 --> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-spring-beans</artifactId> </dependency> <!-- 3. 在 SLF4J 的 MDC (Mapped Diagnostic Context) 中支持 traceId 和 spanId --> <dependency> <groupId>io.zipkin.brave</groupId> <artifactId>brave-context-slf4j</artifactId> </dependency> <!-- 4. 利用 okhttp3 作为 reporter --> <dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-sender-okhttp3</artifactId> </dependency> </dependencies>
个中:
引入 brave-instrumentation-dubbo-rpc,brave 对 dubbo 的支持:https://github.com/openzipkin/brave/blob/master/instrumentation/dubbo-rpc/README.md引入 brave-spring-beans,brave 对 spring bean 的支持:https://github.com/openzipkin/brave/blob/master/spring-beans/README.md引入 brave-context-slf4j,brave 对 SLF4J 的支持,可以在 MDC 中利用 traceId 和 spanId:https://github.com/openzipkin/brave/blob/master/context/slf4j/README.md引入 zipkin-sender-okhttp3,利用 okhttp3 上报数据:https://github.com/openzipkin/zipkin-reporter-java引入 Dubbo 干系依赖
Dubbo 干系的依赖是 Dubbo 本身以及 Zookeeper 客户端,不才面的例子中,我们将会利用独立的 Zookeeper Server 作为做事创造。
<dependencies> <!-- 1. Zookeeper 客户端依赖 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> <exclusions> <exclusion> <groupId>io.netty</groupId> <artifactId>netty</artifactId> </exclusion> </exclusions> </dependency> <!-- 2. Dubbo 依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.2</version> </dependency> </dependencies>
个中:
Dubbo 这里依赖独立的 Zookeeper Server 做做事创造,这里利用的客户端是 Curator引入 Dubbo 框架的依赖,原则上 2.6 的任何版本都是事情的,这里利用的是 2.6.2 版本实现
我们这里布局的场景是一个有两个节点的做事依赖链,也便是,当一个 Dubbo 客户端调用做事 A 时,做事 A 将会连续调用做事 B。在这个例子中,做事 A 是 greeting service,它所依赖的下贱做事做事 B 是 hello service。
定义做事接口
为此须要事先定义两个做事接口 GreetingService 以及 HelloService
com.alibaba.dubbo.samples.api.GreetingServicepackage com.alibaba.dubbo.samples.api;public interface GreetingService { String greeting(String message);}com.alibaba.dubbo.samples.api.HelloService
package com.alibaba.dubbo.samples.api;public interface HelloService { String hello(String message);}
实现做事接口
为了区分对待,所有和 HelloService 干系的实当代码都放在 hello 子包下,同理 GreetingService 干系的放在 greeting 子包下。
实现 com.alibaba.dubbo.samples.api.HelloServicepackage com.alibaba.dubbo.samples.service.hello;import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class HelloServiceImpl implements HelloService { @Override public String hello(String message) { try { // 通过 sleep 仿照业务逻辑处理韶光 Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000)); } catch (InterruptedException e) { // no op } return \"大众hello, \"大众 + message; }}实现 com.alibaba.dubbo.samples.api.GreetingService
package com.alibaba.dubbo.samples.service.greeting;import com.alibaba.dubbo.samples.api.GreetingService;import com.alibaba.dubbo.samples.api.HelloService;import java.util.Random;public class GreetingServiceImpl implements GreetingService { // 下贱依赖做事,运行时靠 spring 容器注入 HelloService 的做事代理 private HelloService helloService; public void setHelloService(HelloService helloService) { this.helloService = helloService; } @Override public String greeting(String message) { try { // 通过 sleep 仿照业务逻辑处理韶光 Thread.sleep(new Random(System.currentTimeMillis()).nextInt(1000)); } catch (InterruptedException e) { // no op } return \公众greeting, \"大众 + helloService.hello(message); }}这里须要把稳的是,GreetingServiceImpl 的实现中声明了一个类型是 HelloService 的成员变量,并在 greeting 方法中,实行完自己逻辑之后又调用了 HelloService 上的 hello 方法。这里的 helloService 的实现将会在运行态由外部注入,注入的不是 HelloServiceImpl 的实现,而是 HelloService 的远程调用代理。通过这样的办法,完成了在一个 Dubbo 做事中连续调用另一个远程 Dubbo 做事的目的。从链路追踪的角度来说,客户端调用 GreetingService 是一个 span,GreetingService 调用 HelloService 是另一个 span,并且两者有父子关系,同属于一个 trace,也便是属于同一条调用链路。其余,在 GreetingServiceImpl 和 HelloServiceImpl 的实现中,通过 Thread.sleep 来仿照了处理业务逻辑的耗时,以便在 Zipkin UI 上更好的展示。
配置
为了专注在展示如何利用 Zipkin 这一点上,本文在配置和编程模型上没有采取更多的高等技能,而是利用了最传统的 Spring XML 的配置办法,帮助读者理解。更高等的通过 annotation 乃至 spring boot 的办法,读者可以自行查阅 Dubbo 和 Zipkin 干系的文档。
暴露 HelloService 做事在 resouces/spring/hello-service.xml 中增加以下的配置来将 HelloServiceImpl 暴露成一个 Dubbo 做事:利用了本地启动的 Zookeeper Server 作为注册中央,地址为默认值 zookeeper://127.0.0.1:2181用 Dubbo 原生做事在端口 20880 上暴露做事将 HelloServiceImpl 注册成 id 是 helloService 的 Spring Bean,这样就可以在后续的 <dubbo:service> 中引用到这个实现类通过 <dubbo:service> 将 HelloServiceImpl 暴露成 Dubbo 做事<!-- 定义 HelloService 的运用名 --> <dubbo:application name=\"大众hello-service-provider\"大众/> <!-- 指定注册中央地址 --> <dubbo:registry address=\公众zookeeper://127.0.0.1:2181\"大众/> <!-- 利用 Dubbo 原生协议在 20880 端口上暴露做事 --> <dubbo:protocol name=\"大众dubbo\"大众 port=\"大众20880\公众/> <!-- 将 HelloServiceImpl 的实现声明成一个 spring bean --> <bean id=\"大众helloService\"大众 class=\"大众com.alibaba.dubbo.samples.service.hello.HelloServiceImpl\"大众/> <!-- 将 HelloServiceImpl 声明成一个 Dubbo 做事 --> <dubbo:service interface=\"大众com.alibaba.dubbo.samples.api.HelloService\"大众 ref=\"大众helloService\"大众/>增加 Zipkin 干系的配置在 resources/spring/hello-service.xml 中增加 Zipkin 干系的配置:修正 dubbo 做事暴露的配置,添加 Zipkin 的 tracing filter 到 Dubbo 的 filter chain 中按照 https://github.com/openzipkin/brave/blob/master/spring-beans/README.md 来配置 Zipkin 的 sender 和 tracing 的 spring bean
<!-- 1. 修正 dubbo 做事暴露配置,在 filter chain 中增加 zipkin 的 tracing 过滤器 --> <dubbo:service interface=\公众com.alibaba.dubbo.samples.api.HelloService\"大众 ref=\公众helloService\公众 filter=\公众tracing\公众/> <!-- 2. zipkin 干系的配置 --> <!-- 利用 OKHttp 来发送 trace 信息到 Zipkin Server。这里的 Zipkin Server 启动在本地 --> <bean id=\公众sender\"大众 class=\"大众zipkin2.reporter.beans.OkHttpSenderFactoryBean\"大众> <property name=\"大众endpoint\"大众 value=\"大众http://localhost:9411/api/v2/spans\"大众/> </bean> <bean id=\公众tracing\公众 class=\公众brave.spring.beans.TracingFactoryBean\公众> <property name=\"大众localServiceName\"大众 value=\公众hello-service\"大众/> <property name=\"大众spanReporter\"大众> <bean class=\"大众zipkin2.reporter.beans.AsyncReporterFactoryBean\"大众> <property name=\"大众sender\"大众 ref=\"大众sender\公众/> <!-- wait up to half a second for any in-flight spans on close --> <property name=\公众closeTimeout\"大众 value=\"大众500\公众/> </bean> </property> <property name=\公众currentTraceContext\"大众> <bean class=\"大众brave.spring.beans.CurrentTraceContextFactoryBean\公众> <property name=\"大众scopeDecorators\公众> <bean class=\公众brave.context.slf4j.MDCScopeDecorator\"大众 factory-method=\公众create\"大众/> </property> </bean> </property> </bean>增加 HelloService 的启动类在 com.alibaba.dubbo.samples.service.hello.Application 中通过 ClassPathXmlApplicationContext 读取 刚才配置的 spring/hello-service.xml 来初始化一个 spring context 并启动
package com.alibaba.dubbo.samples.service.hello;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.io.IOException;public class Application { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(\"大众spring/hello-service.xml\"大众); context.start(); System.out.println(\公众Hello service started\"大众); // press any key to exit System.in.read(); }}暴露 GreetingService 做事,并利用 Zipkin在 resources/spring/greeting-service.xml 中配置 GreetingService。干系步骤与 HelloService 类似,不再赘述,重点关注如何在 GreetingService 中配置下贱做事的依赖。完全的 XML 配置如下:
<beans xmlns:xsi=\公众http://www.w3.org/2001/XMLSchema-instance\"大众 xmlns:dubbo=\"大众http://dubbo.apache.org/schema/dubbo\公众 xmlns=\"大众http://www.springframework.org/schema/beans\"大众 xsi:schemaLocation=\"大众http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd\"大众> <!-- 1. 定义 GreetingService 的运用名 --> <dubbo:application name=\"大众greeting-service-provider\公众/> <!-- 2. 指定注册中央地址 --> <dubbo:registry address=\"大众zookeeper://127.0.0.1:2181\公众/> <!-- 3. 利用 Dubbo 原生协议在 20881 端口上暴露做事 --> <dubbo:protocol name=\"大众dubbo\公众 port=\公众20881\"大众/> <!-- 4. 声明 HelloService 的远程代理,并在 Dubbo 的 filter chain 中增加 tracing filter --> <dubbo:reference id=\"大众helloService\"大众 check=\公众false\公众 interface=\"大众com.alibaba.dubbo.samples.api.HelloService\"大众 filter=\公众tracing\"大众/> <!-- 5. 将 GreetingServiceImpl 的实现声明成一个 spring bean,并将 HelloService 的远程代理装置进去 --> <bean id=\"大众greetingService\"大众 class=\公众com.alibaba.dubbo.samples.service.greeting.GreetingServiceImpl\公众> <property name=\公众helloService\"大众 ref=\公众helloService\"大众/> </bean> <!-- 6. 将 GreetingServiceImpl 声明成一个 Dubbo 做事,并在 Dubbo 的 filter chain 中增加 tracing filter --> <dubbo:service interface=\公众com.alibaba.dubbo.samples.api.GreetingService\公众 ref=\"大众greetingService\"大众 filter=\公众tracing\公众/> <!-- 7. zipkin 干系的配置 --> <bean id=\"大众sender\公众 class=\"大众zipkin2.reporter.beans.OkHttpSenderFactoryBean\公众> <property name=\"大众endpoint\"大众 value=\公众http://localhost:9411/api/v2/spans\"大众/> </bean> <bean id=\"大众tracing\公众 class=\公众brave.spring.beans.TracingFactoryBean\公众> <property name=\"大众localServiceName\"大众 value=\"大众greeting-service\公众/> <property name=\公众spanReporter\公众> <bean class=\公众zipkin2.reporter.beans.AsyncReporterFactoryBean\"大众> <property name=\"大众sender\"大众 ref=\公众sender\"大众/> <!-- wait up to half a second for any in-flight spans on close --> <property name=\公众closeTimeout\"大众 value=\"大众500\公众/> </bean> </property> <property name=\"大众currentTraceContext\"大众> <bean class=\公众brave.spring.beans.CurrentTraceContextFactoryBean\公众> <property name=\"大众scopeDecorators\"大众> <bean class=\"大众brave.context.slf4j.MDCScopeDecorator\公众 factory-method=\公众create\公众/> </property> </bean> </property> </bean></beans>这里的配置与上面的 HelloService 类似,须要重点关注的有两点:第 3 步中把稳做事须要暴露在不同的端口上,否则会和 HelloService 冲突,本例中选择的是 20881 这个端口通过第 4 步先声明 HelloService 的远程代理,然后在第 5 步中将其组装给 GreetingService 来完成做事高下游依赖的声明增加 GreeeingService 的启动类,与 HelloService 类似,通过 spring/greeting-service.xml 的配置来初始化一个新的 spring context 来完成。
package com.alibaba.dubbo.samples.service.greeting; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; public class Application { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(\"大众spring/greeting-service.xml\"大众); context.start(); System.out.println(\"大众Greeting service started\"大众); // press any key to exit System.in.read(); } }实现客户端通过 resources/spring/client.xml 初始化一个 spring context,从个中获取 GreetingService 的远程代理,发起远程调用。
package com.alibaba.dubbo.samples.client;import com.alibaba.dubbo.samples.api.GreetingService;import org.springframework.context.support.ClassPathXmlApplicationContext;public class Application { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(\"大众spring/client.xml\公众); context.start(); // 获取远程代理并发起调用 GreetingService greetingService = (GreetingService) context.getBean(\"大众greetingService\公众); System.out.println(greetingService.greeting(\"大众world\公众)); }}resource/spring/client.xml 中的配置与 Dubbo 做事的配置类似,紧张是配置远程代理,以及配置 Zipkin
<beans xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\公众 xmlns:dubbo=\公众http://dubbo.apache.org/schema/dubbo\公众 xmlns=\"大众http://www.springframework.org/schema/beans\"大众 xsi:schemaLocation=\"大众http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd\公众> <!-- 1. 定义 dubbo 客户真个运用名 --> <dubbo:application name=\"大众dubbo-client\"大众/> <!-- 2. 指定注册中央地址 --> <dubbo:registry address=\"大众zookeeper://127.0.0.1:2181\"大众/> <!-- 3. 声明 GreetingService 的远程代理,并在 Dubbo 的 filter chain 中增加 tracing filter --> <dubbo:reference id=\"大众greetingService\公众 check=\"大众false\"大众 interface=\"大众com.alibaba.dubbo.samples.api.GreetingService\"大众 filter=\公众tracing\"大众/> <!-- 4. zipkin 干系的配置 --> <bean id=\"大众sender\"大众 class=\公众zipkin2.reporter.beans.OkHttpSenderFactoryBean\"大众> <property name=\"大众endpoint\"大众 value=\公众http://localhost:9411/api/v2/spans\"大众/> </bean> <bean id=\"大众tracing\"大众 class=\"大众brave.spring.beans.TracingFactoryBean\"大众> <property name=\"大众localServiceName\公众 value=\公众client\公众/> <property name=\"大众spanReporter\公众> <bean class=\公众zipkin2.reporter.beans.AsyncReporterFactoryBean\"大众> <property name=\"大众sender\公众 ref=\"大众sender\"大众/> <!-- wait up to half a second for any in-flight spans on close --> <property name=\公众closeTimeout\"大众 value=\"大众500\"大众/> </bean> </property> <property name=\公众currentTraceContext\"大众> <bean class=\"大众brave.spring.beans.CurrentTraceContextFactoryBean\公众> <property name=\公众scopeDecorators\"大众> <bean class=\公众brave.context.slf4j.MDCScopeDecorator\"大众 factory-method=\"大众create\"大众/> </property> </bean> </property> </bean></beans>
完成之后的工程的目录构造如下:
运行
现在让我们把全体链路运行起来,看看 Zipkin 链路追踪的效果。
启动 Zookeeper Server
实行以下命令在本地启动一个 Zookeeper Server,如果没有安装,请自行从 ZooKeeper 官网 下载:
$ zkServer start
启动 Zipkin Server
实行以下命令在本地启动一个 Zipkin Server:
$ curl -sSL https://zipkin.io/quickstart.sh | bash -s$ java -jar zipkin.jar
启动 HelloService
利用下面的命令启动 HelloService,当然也可以直接在 IDE 中启动:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.hello.Application
启动成功后该当可以在终端上看到 “Hello service started” 的字样。
启动 GreetingService
利用下面的命令启动 GreetingService,当然也可以直接在 IDE 中启动:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.service.greeting.Application
启动成功后该当可以在终端上看到 “Greeting service started” 的字样。
运行 Dubbo 客户端
利用下面的命令运行 Dubbo 客户端向 GreetingService 发起远程调用,当然也可以直接在 IDE 中运行:
$ mvn exec:java -Dexec.mainClass=com.alibaba.dubbo.samples.client.Application
实行成功后,客户端会在终端上输出 “greeting, hello, world”。
链路追踪
打开浏览器访问 \"大众http://localhost:9411\"大众 并通过 \"大众Find Traces\"大众 按钮来搜索,可以找到刚刚调用的链路追踪,效果如下图所示:
还可以进一步的选择每一个 span 来查看本次调用边界内的详情,比如,hello-service 这个 span 的详情如下:
总结
本文先容了链路追踪的基本观点以及 Zipkin 的基本用法,然后用 Dubbo 构建了一条最大略的调用链路,并引入了 Zipkin 做全链路追踪。由于 Zipkin 对 Dubbo 做了很好的支持,全体集成的过程还是十分大略明了的。
Zipkin 对 Dubbo 的支持是构建在 Dubbo 的 filter 扩展机制上的,有兴趣的读者可以通过 https://github.com/openzipkin/brave/blob/master/instrumentation/dubbo-rpc/src/main/java/brave/dubbo/rpc/TracingFilter.java 理解实在现细节。
本文中涉及的例子可以从 https://github.com/dubbo/dubbo-samples 中的 \公众dubbo-samples-zipkin\"大众 子模块中获取。其余,spring-cloud-sleth 2.0 中开始 正式支持 Dubbo,干系的文章和例子后续操持供应。
作者:中间件小哥