Zipkin 是 Twitter 的一个开源项目,允许开发者收集 Twitter 各个服务上的监控数据,并提供查询接口。分为三个模块,collector、query、web,官网地址是https://twitter.github.io/zipkin/index.html。这个系统的web前端使用了ruby收集数据,然后保存到collector里面。collector的实现有hbase、redis、cassandra等。然后可以通过query查询。关于zipkin的论文有http://bigbully.github.io/Dapper-translation/,它详细的解释了如何跟踪收集日志。
我使用的项目是zipkin-1.1.0,下载地址是https://github.com/twitter/zipkin/releases/tag/1.1.0,启动命令:
collector:bin/sbt 'project zipkin-collector-service' "run -f zipkin-collector-service/config/collector-${SERVICE_TYPE}.scala"
service:bin/sbt 'project zipkin-query-service' "run -f zipkin-query-service/config/query-${SERVICE_TYPE}.scala"
web:bin/sbt 'project zipkin-web' 'run -f zipkin-web/config/web-dev.scala -D local_docroot=zipkin-web/src/main/resources'
启动成功后,就可以在web页面打开查看是否启动成功。
下面分别说一下这三个模块的功能(以存储结构使用hbase):
collector
* Collector Core :封装Collector的服务总线,并定义出存储、索引创建、过滤器、抽样器的接口,通过对上述接口的对外的服务接口封装。Core中只对接口进行了封装,接口的具体实现全部以类似于模板设计模式的方法延迟到了子类去真正实现,提高了代码的扩展性,也使Collector总线服务与具体的存储完全解耦。
* Filter:目前看适用于过滤一些客户端发送到Collector服务的噪音数据。
* Sampler:当服务数据非常庞大的时候,考虑使用采样器对数据进行采样,按照一些概率基础推断应该可以将采样数据看为是全部服务数据的代替(类似于Mentor Carlo方法),目前观察有两种实现,一种为local按均匀分布采样,一种是通过zookeeper中的一些算法进行采样。
* Collector Service:服务的启动方法,通过读取用户的配置文件适配总线服务以及各种不同存储之间的接口实现。
* util:工具类模块。
collector:当前zipkin server实现的存储有Cassandra、Hbase、Redis、RMDB、kafka,类型很丰富,包括了kv NoSQL、列存储 NoSQL、RMDB以及消息中间件。
同时,zipkin collector server端提供了Thrift远程调用方式来进行数据采集。
* 启动
* collector启动时,会根据读取配置文件中的Store.Builder来创建Core总线流程与具体存储接口实现的绑定,具体StorageBuilder、IndexBuilder以及AggregatesBuilder组件的创建过程见各存储实现说明,一下以Hbase为例,代码如下:
val hbaseBuilder = Store.Builder(
hbase.StorageBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181)),
hbase.IndexBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181)),
hbase.AggregatesBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181))
)
CollectorServiceBuilder(Scribe.Interface(categories = Set("zipkin")))
.writeTo(hbaseBuilde
**在CollectorServiceBuilder中,初始化了保存表到接口,StorageService和IndexService,下面是CollectorServiceBuilder到apply工厂方法执行代码:**
def apply() = (runtime: RuntimeEnvironment) => {
serverBuilder.apply().apply(runtime)
log.info("Building %d stores: %s".format(storeBuilders.length, storeBuilders.toString))
val stores = storeBuilders map {
_.apply()
}
**val storeProcessors ****= stores flatMap { store =>
Seq(new StorageService(store.storage), new ClientIndexFilter andThen new IndexService(store.index))
}**
val sampleRate = sampleRateBuilder.apply()
val processor: Service[T, Unit] = {
interface.filter andThen
new SamplerFilter(new ZooKeeperGlobalSampler(sampleRate)) andThen
new ServiceStatsFilter andThen
new FanoutService[Span](storeProcessors)
}
val queue = new WriteQueue(queueMaxSize, queueNumWorkers, processor)
queue.start()
val server = interface.apply().apply(queue,
stores,
new InetSocketAddress(serverBuilder.serverAddress, serverBuilder.serverPort),
serverBuilder.statsReceiver,
serverBuilder.tracer)
/**
* Add config endpoints with the sampleRate endpoint. Available via:
* GET /config/<name>
* POST /config/<name>?value=0.2
*/
val configEndpoints = ("/config/sampleRate", sampleRate) +: additionalConfigEndpoints.map { case (path, builder) =>
("/config/%s".format(path), builder.apply())
}
configEndpoints foreach { case (path, adjustable) =>
serverBuilder.adminHttpService map { _.addContext(path, new ConfigRequestHandler(adjustable)) }
}
/* Start additional services (server sets) */
additionalServices foreach { builder =>
val s = builder.apply().apply(serverBuilder.socketAddress, serverBuilder.statsReceiver, serverBuilder.timer)
s.start()
ServiceTracker.register(s)
}
adaptiveSamplerBuilder foreach { builder =>
val config = builder.apply()
val service = config.apply()
service.start()
ServiceTracker.register(service)
}
**new ZipkinCollector(server)**
}
query:
* query core:定义query到服务接口,初始化查询server。具体到查询方法在启动时指定的query-*.scala实现。
* Adjuster:以某种方式调整输入到trace数据,一般用来调整时间戳或者填充丢失的span
* storage:收集传入到traceid,然后批量到对每个traceid获取执行的延迟时间
* SliceQuery:分片查询接口,只返回制定结束时间戳前到最大的一个。
根据选择到collector(hbase,redis,cassandra..)存储不同,query service也提供了相应到查询实现。内部提供thrift接口。
* 启动:
* query service 启动时,寻找配置文件query-*.scala,加载内部实现,下面以hbase为例:
* val hbaseBuilder = Store.Builder(
hbase.StorageBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181)),
hbase.IndexBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181))
// hbase.AggregatesBuilder(zkServers = Some("192.168.33.100"), zkPort = Some(2181))
)
QueryServiceBuilder(hbaseBuilder)
* query service对外接口:
* 仅提供thrift接口,实例:
* def querySpan(service: String, span: String, annotation: String,
kvAnnotation: (String, ByteBuffer), maxTraces: Int): Unit = {
val protocol = new TBinaryProtocol.Factory()
val serviceClient = ClientBuilder()
}