<audio id="audio" title="16 | 如何搭建一套适合你的服务追踪系统？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/42/bb/428d718973ed04ace64bd3a52a06c3bb.mp3"></audio>

[专栏第8期](http://time.geekbang.org/column/article/15273)我给你讲了服务追踪系统的原理以及实现，简单回顾一下服务追踪系统的实现，主要包括三个部分。

<li>
埋点数据收集，负责在服务端进行埋点，来收集服务调用的上下文数据。
</li>
<li>
实时数据处理，负责对收集到的链路信息，按照traceId和spanId进行串联和存储。
</li>
<li>
数据链路展示，把处理后的服务调用数据，按照调用链的形式展示出来。
</li>

如果要自己从0开始实现一个服务追踪系统，针对以上三个部分你都必须有相应的解决方案。首先你需要在业务代码的框架层开发调用拦截程序，在调用的前后收集相关信息，把信息传输给到一个统一的处理中心。然后处理中心需要实时处理收集到链路信息，并按照traceId和spanId进行串联，处理完以后再存到合适的存储中。最后还要能把存储中存储的信息，以调用链路图或者调用拓扑图的形式对外展示。

可以想象这个技术难度以及开发工作量都不小，对于大部分中小业务团队来说，都十分具有挑战。不过幸运的是，业界已经有不少开源的服务追踪系统实现，并且应用范围也已经十分广泛，对大部分的中小业务团队来说，足以满足对服务追踪系统的需求。

业界比较有名的服务追踪系统实现有阿里的鹰眼、Twitter开源的OpenZipkin，还有Naver开源的Pinpoint，它们都是受Google发布的Dapper论文启发而实现的。其中阿里的鹰眼解决方案没有开源，而且由于阿里需要处理数据量比较大，所以鹰眼的定位相对定制化，不一定适合中小规模的业务团队，感兴趣的同学可以点击本期文章末尾“拓展阅读”进行学习。

下面我主要来介绍下开源实现方案OpenZipkin和Pinpoint，再看看它们有什么区别。

## OpenZipkin

OpenZipkin是Twitter开源的服务追踪系统，下面这张图展示了它的架构设计。

<img src="https://static001.geekbang.org/resource/image/69/33/699916c60cd31a2b8d7ab0335038cf33.png" alt="" /><br />
（图片来源：[https://zipkin.io/public/img/architecture-1.png](https://zipkin.io/public/img/architecture-1.png)）

从图中看，OpenZipkin主要由四个核心部分组成。

<li>
Collector：负责收集探针Reporter埋点采集的数据，经过验证处理并建立索引。
</li>
<li>
Storage：存储服务调用的链路数据，默认使用的是Cassandra，是因为Twitter内部大量使用了Cassandra，你也可以替换成Elasticsearch或者MySQL。
</li>
<li>
API：将格式化和建立索引的链路数据以API的方式对外提供服务，比如被UI调用。
</li>
<li>
UI：以图形化的方式展示服务调用的链路数据。
</li>

它的工作原理可以用下面这张图来描述。

<img src="https://static001.geekbang.org/resource/image/4c/d9/4c036659e0d14176215686f1a1129ed9.png" alt="" /><br />
（图片来源：[https://zipkin.io/pages/architecture.html](https://zipkin.io/pages/architecture.html)）

具体流程是，通过在业务的HTTP Client前后引入服务追踪代码，这样在HTTP方法“/foo”调用前，生成trace信息：TraceId：aa、SpanId：6b、annotation：GET /foo，以及当前时刻的timestamp：1483945573944000，然后调用结果返回后，记录下耗时duration，之后再把这些trace信息和duration异步上传给Zipkin Collector。

## Pinpoint

Pinpoint是Naver开源的一款深度支持Java语言的服务追踪系统，下面这张图是它的架构设计。

<img src="https://static001.geekbang.org/resource/image/d8/a4/d8b526a56b633c34364924a2d00905a4.png" alt="" /><br />
（图片来源：[http://naver.github.io/pinpoint/1.7.3/images/pinpoint-architecture.png](http://naver.github.io/pinpoint/1.7.3/images/pinpoint-architecture.png)）

Pinpoint主要也由四个部分组成。

<li>
Pinpoint Agent：通过Java字节码注入的方式，来收集JVM中的调用数据，通过UDP协议传递给Collector，数据采用Thrift协议进行编码。
</li>
<li>
Pinpoint Collector：收集Agent传过来的数据，然后写到HBase Storgage。
</li>
<li>
HBase Storage：采用HBase集群存储服务调用的链路信息。
</li>
<li>
Pinpoint Web UI：通过Web UI展示服务调用的详细链路信息。
</li>

它的工作原理你可以看这张图。

<img src="https://static001.geekbang.org/resource/image/87/95/8730864e70d666267e40e1cc4d622195.png" alt="" /><br />
（图片来源：[http://naver.github.io/pinpoint/1.7.3/images/td_figure6.png](http://naver.github.io/pinpoint/1.7.3/images/td_figure6.png)）

具体来看，就是请求进入TomcatA，然后生成TraceId：TomcatA^ TIME ^ 1、SpanId：10、pSpanId：-1（代表是根请求），接着TomatA调用TomcatB的hello方法，TomcatB生成TraceId：TomcatA^ TIME ^1、新的SpanId：20、pSpanId：10（代表是TomcatA的请求），返回调用结果后将trace信息发给Collector，TomcatA收到调用结果后，将trace信息也发给Collector。Collector把trace信息写入到HBase中，Rowkey就是traceId，SpanId和pSpanId都是列。然后就可以通过UI查询调用链路信息了。

## 选型对比

根据我的经验，考察服务追踪系统主要从下面这几个方面。

**1. 埋点探针支持平台的广泛性**

OpenZipkin和Pinpoint都支持哪些语言平台呢？

OpenZipkin提供了不同语言的Library，不同语言实现时需要引入不同版本的Library。

官方提供了C#、Go、Java、JavaScript、Ruby、Scala、PHP等主流语言版本的Library，而且开源社区还提供了更丰富的不同语言版本的Library，详细的可以点击[这里](https://zipkin.io/pages/existing_instrumentations)查看；而Pinpoint目前只支持Java语言。

所以从探针支持的语言平台广泛性上来看，OpenZipkin比Pinpoint的使用范围要广，而且开源社区很活跃，生命力更强。

**2. 系统集成难易程度**

再来看下系统集成的难易程度。

以OpenZipkin的Java探针Brave为例，它只提供了基本的操作API，如果系统要想集成Brave，必须在配置里手动里添加相应的配置文件并且增加trace业务代码。具体来讲，就是你需要先修改工程的POM依赖，以引入Brave相关的JAR包。

```
&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
      &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
        &lt;artifactId&gt;brave-bom&lt;/artifactId&gt;
        &lt;version&gt;${brave.version}&lt;/version&gt;
        &lt;type&gt;pom&lt;/type&gt;
        &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
  &lt;/dependencyManagement&gt;

```

然后假如你想收集每一次HTTP调用的信息，你就可以使用Brave在Apache Httpclient基础上封装的httpClient，它会记录每一次HTTP调用的信息，并上报给OpenZipkin。

```
httpclient =TracingHttpClientBuilder.create(tracing).build();

```

而Pinpoint是通过字节码注入的方式来实现拦截服务调用，从而收集trace信息的，所以不需要代码做任何改动。Java字节码注入的大致原理你可以参考下图。

<img src="https://static001.geekbang.org/resource/image/4a/75/4a27448c52515020c1f687e8e3567875.png" alt="" /><br />
（图片来源：[http://naver.github.io/pinpoint/1.7.3/images/td_figure3.png](http://naver.github.io/pinpoint/1.7.3/images/td_figure3.png)）

我来解释一下，就是JVM在加载class二进制文件时，动态地修改加载的class文件，在方法的前后执行拦截器的before()和after()方法，在before()和after()方法里记录trace()信息。而应用不需要修改业务代码，只需要在JVM启动时，添加类似下面的启动参数就可以了。

```
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=&lt;Agent's UniqueId&gt;
-Dpinpoint.applicationName=&lt;The name indicating a same service (AgentId collection)

```

所以从系统集成难易程度上看，Pinpoint要比OpenZipkin简单。

**3. 调用链路数据的精确度**

从下面这张OpenZipkin的调用链路图可以看出，OpenZipkin收集到的数据只到接口级别，进一步的信息就没有了。

<img src="https://static001.geekbang.org/resource/image/33/23/33c924c5563be070416d8133e255af23.jpg" alt="" /><br />
（图片来源：[http://ovcjgn2x0.bkt.clouddn.com/zipkin-info.jpg](http://ovcjgn2x0.bkt.clouddn.com/zipkin-info.jpg)）

再来看下Pinpoint，因为Pinpoint采用了字节码注入的方式实现trace信息收集，所以它能拿到的信息比OpenZipkin多得多。从下面这张图可以看出，它不仅能够查看接口级别的链路调用信息，还能深入到调用所关联的数据库信息。

<img src="https://static001.geekbang.org/resource/image/5f/3e/5f365d3c49cdb113bf6b08f5e3b36e3e.jpg" alt="" /><br />
（图片来源：[http://ovcjgn2x0.bkt.clouddn.com/pp-info.jpg](http://ovcjgn2x0.bkt.clouddn.com/pp-info.jpg)）

同理在绘制链路拓扑图时，OpenZipkin只能绘制服务与服务之间的调用链路拓扑图，比如下面这张示意图。

<img src="https://static001.geekbang.org/resource/image/a7/e9/a7575c0826b77d236ddffe92d4d3c1e9.jpg" alt="" /><br />
（图片来源：[http://ovcjgn2x0.bkt.clouddn.com/zipdependency1.jpg](http://ovcjgn2x0.bkt.clouddn.com/zipdependency1.jpg)）

而Pinpoint不仅能够绘制服务与服务之间，还能绘制与DB之间的调用链路拓扑图，比如下图。

<img src="https://static001.geekbang.org/resource/image/e5/1e/e59d46aa62e542246732ab9a985d281e.jpg" alt="" /><br />
（图片来源：[http://ovcjgn2x0.bkt.clouddn.com/ppreal.jpg](http://ovcjgn2x0.bkt.clouddn.com/ppreal.jpg)）

所以，从调用链路数据的精确度上看，Pinpoint要比OpenZipkin精确得多。

## 总结

今天我给你讲解了两个开源服务追踪系统OpenZipkin和Pinpoint的具体实现，并从埋点探针支持平台广泛性、系统集成难易程度、调用链路数据精确度三个方面对它们进行了对比。

从选型的角度来讲，如果你的业务采用的是Java语言，那么采用Pinpoint是个不错的选择，因为它不需要业务改动一行代码就可以实现trace信息的收集。除此之外，Pinpoint不仅能看到服务与服务之间的链路调用，还能看到服务内部与资源层的链路调用，功能更为强大，如果你有这方面的需求，Pinpoint正好能满足。

如果你的业务不是Java语言实现，或者采用了多种语言，那毫无疑问应该选择OpenZipkin，并且，由于其开源社区很活跃，基本上各种语言平台都能找到对应的解决方案。不过想要使用OpenZipkin，还需要做一些额外的代码开发工作，以引入OpenZipkin提供的Library到你的系统中。

除了OpenZipkin和Pinpoint，业界还有其他开源追踪系统实现，比如Uber开源的Jaeger，以及国内的一款开源服务追踪系统SkyWalking。不过由于目前应用范围不是很广，这里就不详细介绍了，感兴趣的同学可以点击“拓展阅读”自行学习。

## 思考题

OpenZipkin在探针采集完数据后有两种方式把数据传递给Collector，一种是通过HTTP调用，一种是基于MQ的异步通信方式，比如使用RabbitMQ或者Kafka，你觉得哪种方式更好一些？为什么？

欢迎你在留言区写下自己的思考，与我一起讨论。

**拓展阅读：**

阿里巴巴鹰眼：[http://ppt.geekbang.org/slide/download/939/595f4cdcb9d52.pdf/18](http://ppt.geekbang.org/slide/download/939/595f4cdcb9d52.pdf/18)

Jaeger：[https://www.jaegertracing.io](https://www.jaegertracing.io)

SkyWalking：[https://github.com/apache/incubator-skywalking](https://github.com/apache/incubator-skywalking)


