蚂蚁金服分布式链路跟踪组件 SOFATracer 数据上报机制和源码分析 | 剖析

SOFA
Scalable Open Financial Architecture
是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。java


SOFATracer 是一个用于分布式系统调用跟踪的组件,经过统一的 TraceId 将调用链路中的各类网络调用状况以日志的方式记录下来,以达到透视化网络调用的目的,这些链路数据可用于故障的快速发现,服务治理等。
git


本文为《剖析 | SOFATracer 框架》第二篇。《剖析 | SOFATracer 框架》系列由 SOFA 团队和源码爱好者们出品,项目代号:<SOFA:TracerLab/>目前领取已经完成,感谢你们的参与。github


SOFATracer:spring

https://github.com/alipay/sofa-tracerbash

0、前言

在《蚂蚁金服分布式链路跟踪组件 SOFATracer 总览|剖析》一文中已经对 SOFATracer 进行了概要性的介绍。从对 SOFATracer 的定义能够了解到,SOFATracer 做为一个分布式系统调用跟踪的组件,是经过统一的 TraceId 将调用链路中的各类网络调用状况以数据上报的方式记录下来,以达到透视化网络调用的目的。网络

本篇将针对SOFATracer的数据上报方式进行详细分析,以帮助你们更好的理解 SOFATracer 在数据上报方面的扩展。架构


一、Reporter 总体模型

本节将对 SOFATracer 的 Report 模型进行总体介绍,主要包括两个部分:一、Reporter 的接口设计及实现;二、数据上报流程。app

1.一、Reporter 的接口设计及实现

数据上报是 SofaTracer 基于 OpenTracing Tracer 接口扩展实现出来的功能;Reporter 实例做为 SofaTracer 的属性存在,在构造 SofaTracer 实例时,会初始化 Reporter 实例。框架

1.1.一、Reporter 接口设计async

Reporter 接口是 SOFATracer 中对于数据上报的顶层抽象,核心接口方法定义以下:

//获取 Reporter 实例类型
String getReporterType();
//输出 span
void report(SofaTracerSpan span);
//关闭输出 span 的能力
void close();复制代码

Reporter 接口的设计中除了核心的上报功能外,还提供了获取 Reporter 类型的能力,这个是由于 SOFATracer 目前提供的埋点机制方案须要依赖这个实现。

1.1.二、Reporter 接口实现

Reporter 的类体系结构以下:


Reporter 的实现类有两个,SofaTracerCompositeDigestReporterImpl 和 DiskReporterImpl :

  • SofaTracerCompositeDigestReporterImpl:组合摘要日志上报实现,上报时会遍历当前 SofaTracerCompositeDigestReporterImpl 中全部的 Reporter ,逐一执行 report 操做;可供外部用户扩展使用。
  • DiskReporterImpl:数据落磁盘的核心实现类,也是目前 SOFATracer 中默认使用的上报器。


1.二、数据上报流程分析

数据上报实际都是由不一样的链路组件发起,关于插件扩展机制及埋点方式不是本篇范畴,就不展开了。这里直接来看数据上报的入口。

在 Opentracing 规范中提到,Span#finish 方法是 span 生命周期的最后一个执行方法,也就意味着一个 span 跨度即将结束。那么当一个 span 即将结束时,也是当前 span 具备最完整状态的时候。因此在 SOFATracer 中,数据上报的入口就是 Span#finish 方法,这里贴一小段代码:

//SofaTracerSpan#finish
@Override
public void finish(long endTime) {
    this.setEndTime(endTime);
    //关键记录:report span
    this.sofaTracer.reportSpan(this);
    SpanExtensionFactory.logStoppedSpan(this);
}复制代码

在 finish 方法中,经过 SofaTracer#reportSpan 将当前 span 进行了上报处理。以这个为入口,整个数据上报的调用链路以下图所示:

整个上报调用流程其实并非很难,这里留两个问题:

  • 如何构造 clientRportor 和 serverReporter 的,依据是什么?
  • 摘要日志和统计日志是怎么落盘的?

第一个问题会在插件埋点解析篇中给出答案;第二个问题下面来看。


二、日志落盘

前面已经提到,SOFATracer 自己提供了两种上报模式,一种是落到磁盘,另一种是上报到zipkin。在实现细节上,SOFATracer 没有将这两种策略分开以提供独立的功能支持,而是将两种上报方式组合在了一块儿,而后再经过配置参数来控制是否进行具体的上报逻辑,具体参考下图:

本节未来剖析下日志落盘的实现细节。日志落盘又分为摘要日志落盘 和 统计日志落盘;摘要日志是每一次调用均会落地磁盘的日志;统计日志是每隔必定时间间隔进行统计输出的日志。

2.一、摘要日志落盘

摘要日志落盘是基于 Disruptor 高性能无锁循环队列实现的。SOFATracer 中,AsyncCommonDigestAppenderManager 类对 disruptor 进行了封装,用于处理外部组件的 Tracer 摘要日志打印。

关于 Disruptor 的原理及其自身的事件模型此处不展开分析,有兴趣的同窗能够自行查阅相关资料。这里直接看下 SOFATracer 中是如何使用 Disruptor 的。

2.1.一、消息事件模型

SOFATracer 使用了两种不一样的事件模型,一种是 SOFATracer 内部使用的 StringEvent,一种是外部扩展使用的SofaTacerSpanEvent。详见:SofaTracerSpanEvent & StringEvent

2.1.二、Consumer 消费者

Consumer 是 AsyncCommonDigestAppenderManager 的内部类;实现了 EventHandler 接口,这个 Consumer 做为消费者存在,监听事件,而后经过 TraceAppender 将 span 数据 flush 到磁盘。详见:AsyncCommonDigestAppenderManager

2.1.三、Disruptor 的初始化

  • Disruptor 的构建:在 AsyncCommonDigestAppenderManager 的构造函数中完成的。
//构建disruptor,使用的是 ProducerType.MULTI
//等待策略是 BlockingWaitStrategy,考虑到的是CPU的使用率和一致性
disruptor = new Disruptor<SofaTracerSpanEvent>(new SofaTracerSpanEventFactory(),
        realQueueSize, threadFactory, ProducerType.MULTI, new BlockingWaitStrategy());复制代码
  • 异常处理:若是在消费的过程当中发生异常,SOFATracer 将会经过自定义的 ConsumerExceptionHandler 异常处理器把异常信息打到 tracer-self.log 中。

  • 对于打印相关的参数条件设定,好比是否容许丢弃消息、是否记录丢失日志的数量、是否记录丢失日志的 TraceId 和 RpcId、丢失日志的数量达到某阈值进行一第二天志输出等。
2.1.四、启动 Disruptor

Disruptor 的启动委托给了 AsyncCommonDigestAppenderManager#start 方法来执行。

public void start(final String workerName) {
    this.threadFactory.setWorkName(workerName);
    this.ringBuffer = this.disruptor.start();
}复制代码

查看调用栈,看下 SOFATracer 中具体是在哪里调用这个 start 的:

  • CommonTracerManager : 这里面持有了 AsyncCommonDigestAppenderManager 类的一个单例对象,而且在 static 静态代码块中调用了 start 方法;这个用来输出普通中间件日志。
  • SofaTracerDigestReporterAsyncManager:这里类里面也是持有了AsyncCommonDigestAppenderManager 类的一个单例对像,而且提供了getSofaTracerDigestReporterAsyncManager 方法来获取该单例,在这个方法中调用了 start 方法;该对象用来输出摘要日志。

2.1.五、发布事件

发布事件,也就意味着当前须要产生一个 span 记录,这个过程也是在 finish 方法的调用栈中,也就是上图中DiskReporterImpl#digestReport 这个方法。

AsyncCommonDigestAppenderManager asyncDigestManager = SofaTracerDigestReporterAsyncManager
            .getSofaTracerDigestReporterAsyncManager();
// ...
asyncDigestManager.append(span);
// ...复制代码

这里将 span 数据 append 到环形缓冲区,根据 AsyncCommonDigestAppenderManager 的初始化属性,若是容许丢弃,则使用 tryNext 尝试申请序列,申请不到抛出异常;不然使用 next() 阻塞模式申请序列。下面是一个简易的模拟图:

2.1.六、小结

摘要日志的落盘依赖于 Disruptor 的事件模型,当 span#finish 方法执行时,触发 SofaTracer 的 report 行为;report 最终会将当前 span 数据放入 Disruptor 队列中去,发布一个 SofaTracerSpanEvent 事件。Disruptor 的消费者 EventHandler 实现类 Consumer 会监听当前队列事件,而后在回调函数 onEvent 中将 span 数据刷新到磁盘中。

2.二、统计日志落盘实现

统计日志的做用是为了监控统计使用,其记录了当前跨度的调用次数、执行结果等数据。统计日志是每隔必定时间间隔进行统计输出的日志,所以很容易想到是使用按期任务来执行的。这里一样来跟踪下统计日志打印的方法调用过程。

2.2.一、统计日志的调用链路

AbstractSofaTracerStatisticReporter 的 doReportStat 方法是个抽象方法,那这里又是与插件扩展部分联系在一块的:

能够看到 AbstractSofaTracerStatisticReporter 的实现类均是在 SOFATracer plugins 包下,也就是说统计日志打印须要由不一样的扩展插件来定义实现。可是实际上不一样的插件在重写 doReportStat 方法时也并不是是直接将 span 数据 flush 到磁盘的,而是将 SofaTracerSpan 转换成 StatMapKey 而后塞到了 AbstractSofaTracerStatisticReporter 中的一个 map 结构对象中。具体细节详见:[AbstractSofaTracerStatisticReporter#addStat]

2.2.二、统计日志的打印模型

前面提到,统计日志的落盘具备必定的周期性,所以在统计日志落盘的设计上,SOFATracer 没有像摘要日志落盘那样依赖于 Disruptor 来实现。下面先经过一张简单的结构图来看下摘要日志的工做模型:

  • xxxxxStatReporter : 插件扩展方实现的统计日志 Reporter 类,重写了 doStatReport 和 print 两个方法。
  • AbstractSofaTracerStatisticReporter : 用于扩展的抽象类,xxxxxStatReporter 就是该类的子类;AbstractSofaTracerStatisticReporter 在其构造函数中,经过 SofaTracerStatisticReporterCycleTimesManager 将当前 statReporter 注册到 SofaTracerStatisticReporterManager 中,统一存放在 statReporters 集合中。
  • SofaTracerStatisticReporterManager : 统计日志 reporter 管理器,全部插件扩展的 reporter 都会被注册到这个manager 类里面来。其内部类 StatReporterPrinter 实现了runnable 接口,并在 run 方法中遍历 statReporters,逐一调用 print 方法将数据刷到磁盘中。

SofaTracerStatisticReporterManager 在构造函数中初始化了任务执行的周期、ScheduledExecutorService 实例初始化,而且将 StatReporterPrinter 提交到定时任务线程池中,从而实现了周期性输出统计日志的功能。


三、上报 Zipkin

前面对 SOFATracer 中的数据落盘进行了分析,最后再来看下 SOFATracer 中是如何把数据上报至 zipkin 的。

3.一、上报 zipkin 的流程

接着上面的分析,SOFATracer 中的数据上报策略是以组合的形式共存的,这里能够结合 第2节的第一张图 来看。这里先给出 zipkin 上报的流程,而后再结合流程展开分析:

  • 在SofaTracer#reportSpan 中有一个方法是 invokeReportListeners;该方法的做用就是遍历当前全部的SpanReportListener 实现类,逐一回调 SpanReportListener 的 onSpanReport 方法。
  • ZipkinSofaTracerSpanRemoteReporter 是 sofa-tracer-zipkin-plugin 插件中提供的一个实现了 SpanReportListener 接口的类,并在 onSpanReport 回调函数中经过 zipkin2.reporter.AsyncReporter 实例对象将 span 数据上报至 zipkin。
  • 虽然 SOFATracer 和 zipkin 均是基于 OpenTracing 规范,可是在具体实现上 SOFATracer 作了不少扩展,所以须要经过一个 ZipkinV2SpanAdapter 将 SofaTracerSpan 适配成 zipkin2.Span。

zipkin2.reporter.AsyncReporter 是 zipkin 提供的一个数据上报抽象类,默认实现是 BoundedAsyncReporter,其内部经过一个守护线程 flushThread,一直循环调用 BoundedAsyncReporter 的 flush 方法,将内存中的 span 信息上报给 zipkin。

3.二、对非 SpringBoot 应用的上报支持

上报 zipkin 的能力作过一次改动,主要是对于在非SpringBoot应用(也就是Spring工程)的支持,具体参考 issue:建议不用spring boot也可使用sofa-tracer而且上报zipkin

对于 SpringBoot 工程来讲,引入 tracer-sofa-boot-starter 以后,自动配置类 SofaTracerAutoConfiguration 会将当前全部 SpanReportListener 类型的 bean 实例保存到 SpanReportListenerHolder 的 List 对象中。而SpanReportListener 类型的 Bean 会在 ZipkinSofaTracerAutoConfiguration 自动配置类中注入到当前 Ioc 容器中。这样 invokeReportListeners 被调用时,就能够拿到 zipkin 的上报类,从而就能够实现上报。

对于非 SpringBoot 应用的上报支持,本质上是须要实例化 ZipkinSofaTracerSpanRemoteReporter 对象,并将此对象放在 SpanReportListenerHolder 的 List 对象中。因此 SOFATracer 在 zipkin 插件中提供了一个ZipkinReportRegisterBean,并经过实现 Spring 提供的 bean 生命周期接口 InitializingBean,在ZipkinReportRegisterBean 初始化以后构建一个 ZipkinSofaTracerSpanRemoteReporter 实例,并交给SpanReportListenerHolder 类管理。

3.三、Zipkin 上报案例及展现

关于 SpringBoot 工程使用 zipkin 上报案例请参考:上报数据到 zipkin

关于 spring 应用中使用 zipkin 上报插件请参考:tracer-zipkin-plugin-demo

  • Services 展现

  • 链路依赖展现


四、总结

4.一、SOFATracer 在数据上报模型上的考虑

了解或者使用过 SOFATracer 的同窗应该知道, SOFATracer 目前并无提供数据采集器和 UI 展现的功能;主要有两个方面的考虑:

  • SOFATracer 做为 SOFA 体系中一个很是轻量的组件,意在将 span 数据以日志的方式落到磁盘,以便于用户可以更加灵活的来处理这些数据
  • UI 展现方面,SOFATracer 自己基于 OpenTracing 规范实现,在模型上与开源的一些产品能够实现无缝对接,在必定程度上能够弥补自己在链路可视化方面的不足。

所以在上报模型上,SOFATracer 提供了日志输出和外部上报的扩展,方便接入方可以足够灵活的方式来处理上报的数据。

4.二、文章小结

经过本文你们对 SOFATracer 数据上报功能应该有了一个大致的了解,对于内部的实现细节,因为篇幅和文章阅读性等缘由,不宜贴过多代码,但愿有兴趣的同窗能够直接阅读源码,对其中的一些细节进行了解。数据上报做为 SOFATracer 核心扩展能力之一,虽不一样的上报途径对应不一样的上报模型,可是总体结构上仍是比较清晰的,因此理解起来不是很难。

最后感谢你们对 SOFATracer 的关注,若是您在了解和使用此组件的过程当中有任何疑问,欢迎联系咱们。


 欢迎加入,参与 SOFATracer 源码解析【已领取完毕】

本文做为《剖析 | SOFATracer 组件系列》第一篇,主要仍是但愿你们对 SOFATracer 组件有一个认识和了解,以后,咱们会逐步详细介绍每部分的代码设计和实现,预计会按照以下的目录进行:

  • 分布式链路跟踪组件 SOFATracer 概述【已完成】

  • SOFATracer API 组件埋点机制和源码分析【已完成】

  • SOFATracer 链路透传原理与 SLF4J MDC 的扩展能力分析【已领取】

  • SOFATracer 的采样策略和源码分析【已领取】

  • SOFATracer 数据上报机制和源码分析【已领取】


文中提到的文字连接

长按关注,获取分布式架构干货

欢迎你们共同打造 SOFAStack https://github.com/alipay

相关文章
相关标签/搜索