概述
Twitter的zipkin是一个致力于收集Twitter所有的分布式服务的时间数据的分布式跟踪系统。它提供了收集数据,和查询数据两大服务。系统的理论模型来自于Google Dapper 论文。Dapper这篇论文可以点击这里。
为什么需要分布式跟踪系统?
通过采集跟踪数据可以帮助开发者深入了解在分布式系统中某一个特定的请求时如何执行的。假如说,我们现在有一个用户请求超时,我们就可以将这个超时的请求调用链展示在UI当中。我们可以很快度的定位到导致响应很慢的服务究竟是什么。如果对这个服务细节也很很清晰,那么我们还可以定位是服务中的哪个问题导致超时。
架构
一般来说,分布式跟踪系统由以下组件构成。
封装基础框架
跟踪的信息是通过封装好的框架,部署在每台机器上面,然后发送到zipkin。当主机对某一个服务发起请求时,请求所经过的路径都会被跟踪记录下来,并且最终会在zipkin 的服务器端把这些调用链组装起来。Twitter对一些的基础框架作了封装,以便于对请求做拦截,并且在拦截的同时,还会把 Trace header 传递到下一层。
Finagle
Finagle 是Twitter的 SOA 框架,支持Java/Scala两种语言。
Finagle 在Twitter被广泛使用,通过finagle来支持跟踪是一个非常自然的入口点。目前twitter可以使它支持客户端/服务端的Thrift协议和HTTP协议,对于Memecache和Redis仅能做到客户端支持。使用Scala语言来建立一个 Finagle Server,非常简单,如下。想要加入跟踪功能,只需要把tracer 作为参数传入,构建即可。
ServerBuilder().codec(ThriftServerFramedCodec())
.bindTo(serverAddr)
.name("servicename")
.tracer(ZipkinTracer.mk())
.build(new SomeService.FinagledService(queryService, new TBinaryProtocol.Factory()))
跟踪客户端也是类似的。一旦这样做了之后,请求就会被自动记录下来。包括客户端请求的开始和结束、服务端的开始和结束。
如果你想记录一些自定义的内容,zipkin还提供了如下的API:记录 timestamp / value
Trace.record("starting that extremely expensive computation")
记录key/value
Trace.recordBinary("http.response.code", "500")
Ruby Thrift
Ruby thrift 是对ruby语言的支持。
Querulous
Querulous 是一个Scala的访问数据的框架,可以理解为Java中的hibernate,用它就可以跟踪所有的sql访问情况。
Cassie
Cassie是一个基于Finagle的Cassandra客户端。可以和Finagle一样设置跟踪功能。
KeyspaceBuilder.cluster.keyspace(keyspace).tracer(ZipkinTracer.mk())
传输
Twitter使用scirbe来把所有的跟踪数据传输到zipkin的后端和hadoop文件系统。scribe是facebook开发的,运行在每台机器上面。
Zipkin collector 进程
一旦搜集的数据到达zipkin的后端,服务器端会对数据进行校验、存储,索引。
Storage
数据最开始是放在cassandra数据库中,当然zipkin在存储这块,做到可插播,目前已经做到支持redis,Hbase,MySQL,PostgreSQL, SQLite, and H2。
Zipkin query 进程
当数据被索引和存储之后,zipkin还提供一个查询进程用于获取数据。查询接口也是基于Thrift协议来实现了,做到了与语言无关性。
UI
zipkin的数据可视化是通过一个叫做D3的js库来实现的。
模块
怎么去封装一个框架
在Twitter,zipkin已经被集成到一些基础的框架和类库之中,对于开发者来说,如果想要封装其他的框架,就需要对跟踪的数据模型有一个清晰的认识:
- annotation :包括一个值,时间戳,主机名
- span :一组代表一次RPC请求所包含的annotations
- Trace :一组代表一次用户请求所包含的spans,其中根span只有一个。
另外,还需要把一些轻量级的TraceID和Span ID在服务之间传递,这样才能形成一个完整的调用链。
跟踪的头信息包括以下:
- Trace Id - 全局的 id
- Span Id - 每个方法调用的id
- Optional Parent Span Id - 当前方法调用的父方法的 span id
- Sampled boolean - 是否需要采样
以HTTP协议为例子,具体的封装步骤如下:
服务端
检查http header中是否存在Trace header信息,如果存在就把这些信息取出来,然后存入到ThreadLocal中,进而把请求串接起来;
如果不存在,那么就开始一个新的Trace
客户端
客户端需要把Trace header的信息放在ThreadLocal中,
如果需要进行RPC调用,需要把Trace header的信息放在协议中,例如http header。
github 地址
Twitter zipkin github 地址 https://github.com/twitter/zipkin