分布式跟踪系统zipkin简介

1.概述

Zipkin是根据Google Dapper的论文设计的全链路监控系统,由Twitter公司开发。

Zipkin 以 Trace 结构表示对一次请求的追踪,又把每个 Trace 拆分为若干个有依赖关系的 Span。在微服务架构中,一次用户请求可能会由后台若干个服务负责处理,那么每个处理请求的服务就可以理解为一个 Span(可以包括 API 服务,缓存服务,数据库服务以及报表服务等)。当然这个服务也可能继续请求其他的服务,因此 Span 是一个树形结构,以体现服务之间的调用关系。

2.架构图

zipkin架构图

虚线为zipkin-server提供的功能,主要包括四个模块:

  • Collector 接受收集zipkin的客户端传输的数据。
  • Storage 存储收集来的数据,默认是Memory,可配置为MySQL,Cassandra,ES等。
  • API 负责查询Storage中存储的数据,提供给UI使用。
  • UI 提供简单的web页面。

Instrumented为采集数据并将数据发送给zipkin的客户端。其主要采集的数据结构为Trace和Span。

3.Brave

Brave是Java版的zipkin客户端。

我们一般不会手动编写Trace相关的代码,Brave提供可一些开箱即用的库,帮助我们追踪一些特定的请求。比如dubbo,grpc,servlet,mysql,httpClient,kafka,springMVC等。

    /**
     *  客户端具体怎么收集数据的demo
     */
    public static void main(String[] args) throws Exception{
        // 1.构建客户端发送工具
        Sender sender = OkHttpSender.create("http://127.0.0.1:9411/api/v2/spans");
        // 2.构建异步reporter
        AsyncReporter asyncReporter = AsyncReporter.builder(sender)
                .closeTimeout(500, TimeUnit.MILLISECONDS)
                .build(SpanBytesEncoder.JSON_V2);
        // 3.构建tracing上下文
        Tracing tracing = Tracing.newBuilder()
                .localServiceName("myService")
                .spanReporter(asyncReporter)
                .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY,"shuang"))
                .currentTraceContext(ThreadContextCurrentTraceContext.create())
                .build();
        // 4.使用tracer创建span并操作start()和finish()方法
        Tracer tracer = tracing.tracer();
        Span span = tracer.newTrace().name("total").start();

        Span action_1 = tracer.newChild(span.context()).name("action-1").start();
        try {
            Thread.sleep(500);
        }finally {
            action_1.finish();
        }

        Span action_2 = tracer.newChild(span.context()).name("action-2").start();
        try {
            Thread.sleep(500);
        }finally {
            action_2.finish();
        }

        try {
            Thread.sleep(2000);
        }finally {
            span.finish();
        }
    }
复制代码

3.Tracing简介

Tracing中依赖的几个重要类

  • Endpoint IP,端口,应用服务名等信息
  • Reporter 采集数据提报器
  • Sampler 采样器,根据TraceId来判断该Trace是否需要被采样
  • CurrentTraceContext 将TraceContext绑定到ThreadLocal中,以便当前线程获取
  • Propagation 是一个可以向数据携带的对象carrier上注入和提取数据的接口

3.1Sampler

public abstract class Sampler {
    /**
     *  根据traceId是否需要采样
     */
    public abstract boolean isSampled(long traceId);
}
复制代码

其提供了几个默认的采样器实现。ALWAYS_SAMPLE,NERVER_SAMPLE和一个可定义采样率的采样器CountingSampler。

4.Span简介

采集数据和发送数据的核心接口为Span.start()和Span.finish()。

  /**
   *  RealSpan依赖于Trace上下文,Recorder。
   */
  static RealSpan create(TraceContext context, Clock clock, Recorder recorder) {
    return new AutoValue_RealSpan(context, clock, recorder);
  }
  
  public Span start(long timestamp) {
    recorder().start(context(), timestamp);
    return this;
  }
  
  public void finish(long timestamp) {
    recorder().finish(context(), timestamp);
  }
  
  public void finish(TraceContext context, long finishTimestamp) {
    MutableSpan span = spanMap.remove(context);
    if(span == null || noop.get()) return;
    synchronized (span) {
        span.finsh(finishTimestamp);
        reporter.report(span.toSpan());
    }
  }
复制代码

RealSpan调用start()和finish(),获取和TraceContext绑定的Span信息,记录开始时间和结束时间,并在结束时,调用reporter的report方法,上报给zipkin。

5.与springMVC整合

Brave利用springMVC提供的拦截器机制,在拦截前后分别调用前面的span相关代码,即做到对一次请求的监控。

   <!-- Brave提供的拦截器 -->
   <mvc:interceptors>
    <bean class="brave.spring.webmvc.TracingHandlerInterceptor" factory-method="create">
      <constructor-arg type="brave.http.HttpTracing" ref="httpTracing"/>
    </bean>
  </mvc:interceptors>
复制代码

在preHandle() 和 afterCompletion() 中分别调用span.start()和span.finish()方法完成对zipkin服务端的上报。详见相关代码。

6.与Mysql整合

Mysql也在执行sql前后也提供了一个拦截接口。

 /**
  * mysql-connector.jar中提供的拦截接口
  */
 public interface StatementInterceptorV2 extends Extension {
    void init(Connection var1, Properties var2) throws SQLException;

    ResultSetInternalMethods preProcess(String var1, Statement var2, Connection var3) throws SQLException;

    boolean executeTopLevelOnly();

    void destroy();

    ResultSetInternalMethods postProcess(String var1, Statement var2, ResultSetInternalMethods var3, Connection var4, int var5, boolean var6, boolean var7, SQLException var8) throws SQLException;
}
复制代码

在preProcess()中执行span.start(), 在postProcess()中执行span.finish()。详见相关代码。

7.其他中间件整合略。

8.总结

对zipkin的简单介绍。良好的框架设计会给开发者带来许多便利。(zipkin的客户端大多是基于拦截接口实现的。)