SOFA
Scalable Open Financial Architecture
是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。java
SOFATracer 是一个用于分布式系统调用跟踪的组件,经过统一的 TraceId 将调用链路中的各类网络调用状况以日志的方式记录下来,以达到透视化网络调用的目的,这些链路数据可用于故障的快速发现,服务治理等。
git
本文为《剖析 | SOFATracer 框架》第二篇。《剖析 | SOFATracer 框架》系列由 SOFA 团队和源码爱好者们出品,项目代号:<SOFA:TracerLab/>,目前领取已经完成,感谢你们的参与。github
SOFATracer:spring
https://github.com/alipay/sofa-tracerbash
在《蚂蚁金服分布式链路跟踪组件 SOFATracer 总览|剖析》一文中已经对 SOFATracer 进行了概要性的介绍。从对 SOFATracer 的定义能够了解到,SOFATracer 做为一个分布式系统调用跟踪的组件,是经过统一的 TraceId 将调用链路中的各类网络调用状况以数据上报的方式记录下来,以达到透视化网络调用的目的。网络
本篇将针对SOFATracer的数据上报方式进行详细分析,以帮助你们更好的理解 SOFATracer 在数据上报方面的扩展。架构
本节将对 SOFATracer 的 Report 模型进行总体介绍,主要包括两个部分:一、Reporter 的接口设计及实现;二、数据上报流程。app
数据上报是 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 :
数据上报实际都是由不一样的链路组件发起,关于插件扩展机制及埋点方式不是本篇范畴,就不展开了。这里直接来看数据上报的入口。
在 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 进行了上报处理。以这个为入口,整个数据上报的调用链路以下图所示:
整个上报调用流程其实并非很难,这里留两个问题:
第一个问题会在插件埋点解析篇中给出答案;第二个问题下面来看。
前面已经提到,SOFATracer 自己提供了两种上报模式,一种是落到磁盘,另一种是上报到zipkin。在实现细节上,SOFATracer 没有将这两种策略分开以提供独立的功能支持,而是将两种上报方式组合在了一块儿,而后再经过配置参数来控制是否进行具体的上报逻辑,具体参考下图:
本节未来剖析下日志落盘的实现细节。日志落盘又分为摘要日志落盘 和 统计日志落盘;摘要日志是每一次调用均会落地磁盘的日志;统计日志是每隔必定时间间隔进行统计输出的日志。
摘要日志落盘是基于 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,使用的是 ProducerType.MULTI
//等待策略是 BlockingWaitStrategy,考虑到的是CPU的使用率和一致性
disruptor = new Disruptor<SofaTracerSpanEvent>(new SofaTracerSpanEventFactory(),
realQueueSize, threadFactory, ProducerType.MULTI, new BlockingWaitStrategy());复制代码
Disruptor 的启动委托给了 AsyncCommonDigestAppenderManager#start 方法来执行。
public void start(final String workerName) {
this.threadFactory.setWorkName(workerName);
this.ringBuffer = this.disruptor.start();
}复制代码
查看调用栈,看下 SOFATracer 中具体是在哪里调用这个 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.一、统计日志的调用链路
AbstractSofaTracerStatisticReporter 的 doReportStat 方法是个抽象方法,那这里又是与插件扩展部分联系在一块的:
能够看到 AbstractSofaTracerStatisticReporter 的实现类均是在 SOFATracer plugins 包下,也就是说统计日志打印须要由不一样的扩展插件来定义实现。可是实际上不一样的插件在重写 doReportStat 方法时也并不是是直接将 span 数据 flush 到磁盘的,而是将 SofaTracerSpan 转换成 StatMapKey 而后塞到了 AbstractSofaTracerStatisticReporter 中的一个 map 结构对象中。具体细节详见:[AbstractSofaTracerStatisticReporter#addStat]。
2.2.二、统计日志的打印模型
前面提到,统计日志的落盘具备必定的周期性,所以在统计日志落盘的设计上,SOFATracer 没有像摘要日志落盘那样依赖于 Disruptor 来实现。下面先经过一张简单的结构图来看下摘要日志的工做模型:
SofaTracerStatisticReporterManager 在构造函数中初始化了任务执行的周期、ScheduledExecutorService 实例初始化,而且将 StatReporterPrinter 提交到定时任务线程池中,从而实现了周期性输出统计日志的功能。
前面对 SOFATracer 中的数据落盘进行了分析,最后再来看下 SOFATracer 中是如何把数据上报至 zipkin 的。
接着上面的分析,SOFATracer 中的数据上报策略是以组合的形式共存的,这里能够结合 第2节的第一张图 来看。这里先给出 zipkin 上报的流程,而后再结合流程展开分析:
zipkin2.reporter.AsyncReporter 是 zipkin 提供的一个数据上报抽象类,默认实现是 BoundedAsyncReporter,其内部经过一个守护线程 flushThread,一直循环调用 BoundedAsyncReporter 的 flush 方法,将内存中的 span 信息上报给 zipkin。
上报 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 类管理。
关于 SpringBoot 工程使用 zipkin 上报案例请参考:上报数据到 zipkin
关于 spring 应用中使用 zipkin 上报插件请参考:tracer-zipkin-plugin-demo
了解或者使用过 SOFATracer 的同窗应该知道, SOFATracer 目前并无提供数据采集器和 UI 展现的功能;主要有两个方面的考虑:
所以在上报模型上,SOFATracer 提供了日志输出和外部上报的扩展,方便接入方可以足够灵活的方式来处理上报的数据。
经过本文你们对 SOFATracer 数据上报功能应该有了一个大致的了解,对于内部的实现细节,因为篇幅和文章阅读性等缘由,不宜贴过多代码,但愿有兴趣的同窗能够直接阅读源码,对其中的一些细节进行了解。数据上报做为 SOFATracer 核心扩展能力之一,虽不一样的上报途径对应不一样的上报模型,可是总体结构上仍是比较清晰的,因此理解起来不是很难。
最后感谢你们对 SOFATracer 的关注,若是您在了解和使用此组件的过程当中有任何疑问,欢迎联系咱们。
本文做为《剖析 | SOFATracer
组件系列》第一篇,主要仍是但愿你们对 SOFATracer
组件有一个认识和了解,以后,咱们会逐步详细介绍每部分的代码设计和实现,预计会按照以下的目录进行:
分布式链路跟踪组件 SOFATracer
概述【已完成】
SOFATracer API
组件埋点机制和源码分析【已完成】
SOFATracer
链路透传原理与 SLF4J MDC
的扩展能力分析【已领取】
SOFATracer
的采样策略和源码分析【已领取】
SOFATracer
数据上报机制和源码分析【已领取】
长按关注,获取分布式架构干货
欢迎你们共同打造 SOFAStack https://github.com/alipay