Zipkin是一个分布式跟踪系统,Zipkin的设计是基于谷歌的Google Dapper论文,它可以帮助收集时间数据,在microservice架构下,通过链路追踪,可以便捷的分析服务调用延迟问题。每个应用程序向Zipkin server端报告数据,Zipkin UI呈现了一个依赖图表来展示多少跟踪请求经过了每个应用程序。
追踪器驻留在应用程序里,并且记录发生操作的时间和元数据。他们经常装配在库上,所以对用户来说是透明的。举个例子,一个装配过的 Web 服务器,会在接收请求和发送响应进行记录。收集的追踪数据叫做 Span(跨度)。用于向 Zipkin 发送数据的组件叫做 Reporter。Reporter 通过 Transport 发送追踪数据到 Zipkin 的 Collector,Collector 持久化数据到 Storage 中。之后,API 从 Storage 中查询数据提供给 UI。
如下图所示,装配库发送的跨度必须由装配的服务Transport传输到 Collector,当追踪数据抵达Zipkin Collector守护进程,Zipkin Collector为了查询,会对其进行校验、存储和索引。Storage部分,Zipkin最初将数据存储在Cassandra中,因为Cassandra易跨站,支持灵活的schema,并且在Twitter内部被大规模使用。后来将Storage修改成了可插拔式的,除Cassandra之外,原生支持ElasticSearch和MySQL,可作为第三方扩展提供给其它后端。一旦数据被存储索引,就需要一种方式提取它。查询守护进程提供了一个简单的JSON API查询和获取追踪数据。API的主要消费者就是Web UI。Web UI提供了基于服务、时间和标记(annotation)查看追中数据的方法。
上面是从架构方面来了解Zipkin,接下来了解一些链路追踪的基本概念。
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 到此结束
如下图所示,在一个简单的应用中埋点后,即可在Zipkin server的UI上查看到链路信息,可以看到链路中显示了Span Id、Parent Id、Annotation等信息。除了这些信息外,还有tag信息,tag如果不进行自定义的话,就显示默认生成的tag信息。
那如何在应用中实现埋点收集应用的追踪数据呢?Zipkin支持很多Instrument libraries,支持不同技术栈语言,对于Java而言,Instrument Libraries可以选择Brave或者Spring-cloud-sleuth等。Brave是Zipkin官网提供的Instrument Library,此篇博客使用Brave Library,介绍如何在客户端应用埋点完成trace数据收集。实际Zipkin官网提供了用Brave编写的多个example例子。以webmvc4-boot example为例,最重要的配置文件是TracingAutoConfiguration。为了实现埋点收集数据,需要创建Tracing对象。
如上图所示,在创建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。
在CurrentTraceContext中可以添加 ScopeDecorator,通过 MDC (Mapped Diagnostic Contexts)机制关联一些日志框架:
brave-context-slf4j SLF4J
brave-context-log4j2 Log4J 2
brave-context-log4j12 Log4J v1.2
为了在Zipkin中显示链路显示,除了在webmvc4-boot启动Frontend和Backend服务外,还需要启动Zipkin服务端应用,启动命令如下:服务端启动成功后,即可在Zipkin UI中查看链路调用信息。
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
上面介绍了采用brave instrument采集trace信息,对于Java应用还可以用Spring-cloud-sleuth。Spring-cloud-sleuth使用brave框架完成trace信息收集。如果要使用spring-cloud-sleuth,首先在pom.xml文件中添加相关依赖。下面是frontend服务的主要代码,可以看到自动注入了RestTemplate,通过RestTemplate调用后端服务。因为注入了一个RestTemplate拦截器,故可以将所有跟踪信息传递给请求。每次调用时,会创建一个新的Span。需要注意一点是:必须将RestTemplate注册为bean,以便注入拦截器。如果使用new关键字创建RestTemplate实例,instrument将不起作用。
在backend服务中,如果要在跟踪信息中自定义tag等,那么可以通过tracer对象添加tag信息,如果想自定义追踪的信息,那么可以通过设置SleuthCommonRequestFilter完成。例如,下面设置了filter.setIncludeHeader(true),那么在上传到zipkin的追踪信息中就包含每次请求的header信息。
利用Tracer对象可以创建新的span,给span定义tag等,具体代码如下所示:
启动zipkin server端,启动frontend服务和backend服务,调用frontend的api,即可在zipkin上查看到链路追踪信息。追踪信息如下图所示,可以看到自定义的tag信息,以及追踪信息里面包含header等信息。
上面演示的Demo代码都已经上传到了github,可以下载进行尝试。