SOFAandroid
Scalable Open Financial Architecture 是蚂蚁金服自主研发的金融级分布式中间件,包含了构建金融级云原生架构所需的各个组件,是在金融场景里锤炼出来的最佳实践。git
SOFATracer 是一个用于分布式系统调用跟踪的组件,经过统一的 TraceId 将调用链路中的各类网络调用状况以日志的方式记录下来,以达到透视化网络调用的目的,这些链路数据可用于故障的快速发现,服务治理等。github
本文为《剖析 | SOFATracer 框架》最后一篇,本篇做者sqyu,来自小象生鲜。《剖析 | SOFATracer 框架》系列由 SOFA 团队和源码爱好者们出品,项目代号:<SOFA:TracerLab/> ,目前已经所有完成,可在文末获取本系列文章目录,感谢你们的参与。web
SOFATracer:后端
自 Google《Dapper,大规模分布式系统的跟踪系统》论文发表以来,开源 Tracer 系统如雨后春笋般相继面市,各显神通,但都是用于分布式系统调用跟踪的组件,经过统一的 traceId 将调用链路中的各类网络调用状况记录下来,以达到透视化网络调用的目的。本文介绍的 SOFATracer 是以日志的形式来记录的,这些日志可用于故障的快速定位,服务治理等。目前来看 SOFATracer 团队已经为咱们搭建了一个完整的 Tracer 框架内核,包括数据模型、编码器、跨进程透传 traceId、采样、日志落盘与上报等核心机制,并提供了扩展 API 及基于开源组件实现的部分插件,为咱们基于该框架打造本身的 Tracer 平台提供了极大便利。bash
做为一个开源实现,SOFATracer 也尽量提供大而全的插件实现,但因为多数公司都有本身配套的技术体系,彻底依赖官方提供的插件可能没法知足自身的须要,所以如何基于 SOFATracer 自身 API 的组件埋点机制进行扩展,实现本身的插件是必须掌握的一项本领。cookie
本文将根据 SOFATracer 自身 API 的扩展点及已提供的插件源码来分析下 SOFATracer 插件的埋点机制。网络
对一个应用的跟踪要关注的无非就是 客户端->web 层->rpc 服务->dao 后端存储、cache 缓存、消息队列 mq 等这些基础组件。SOFATracer 插件的做用实际上也就是对不一样组件进行埋点,以便基于这些组件采集应用的链路数据。架构
不一样组件有不一样的应用场景和扩展点,所以对插件的实现也要因地制宜,SOFATracer 埋点方式通常是经过 Filter、Interceptor 机制实现的。
SOFATracer 目前已实现的插件中,像 SpringMVC 插件是基于 Filter 进行埋点的,httpclient、resttemplate 等是基于 Interceptor 机制进行埋点的。在实现插件时,要根据不一样插件的特性和扩展点来选择具体的埋点方式。正所谓条条大路通罗马,无论怎么实现埋点,都是依赖 SOFATracer 自身 API 的扩展机制来实现。
SOFATracer 中全部的插件均须要实现本身的 Tracer 实例,如 SpringMVC 的 SpringMvcTracer 、HttpClient 的 HttpClientTracer 等。
基于 SOFATracer API 埋点方式插件扩展以下:
AbstractTracer 是 SOFATracer 用于插件扩展使用的一个抽象类,根据插件类型不一样,又能够分为 clientTracer 和 serverTracer,分别对应于 AbstractClientTracer 和 AbstractServerTracer;再经过 AbstractClientTracer 和 AbstractServerTracer 衍生出具体的组件 Tracer 实现,好比上图中提到的 HttpClientTracer 、RestTemplateTracer 、SpringMvcTracer 等插件 Tracer 实现。
AbstractTracer
这里先来看下 AbstractTracer 这个抽象类中具体提供了哪些抽象方法,也就是对于 AbstractClientTracer 和 AbstractServerTracer 须要分别扩展哪些能力。
从上图 AbstractTracer 类提供的抽象方法来看,不论是 client 仍是 server,在具体的 Tracer 插件实现中,都必须提供如下实现:
DigestReporterLogName :当前组件摘要日志的日志名称
DigestReporterRollingKey : 当前组件摘要日志的滚动策略
SpanEncoder:对摘要日志进行编码的编码器实现
AbstractSofaTracerStatisticReporter : 统计日志 reporter 类的实现类
基于 SOFATracer 自身 API 埋点最大的优点在于能够经过上面的这些参数来实现不一样组件日志之间的隔离,上述须要实现的这些点是实现一个组件埋点常规的扩展点,是不可缺乏的。
上面分析了 SOFATracer API 的埋点机制,而且对于一些须要扩展的核心点进行了说明。SOFATracer 自身提供的内核很是简单,其基于自身 API 的埋点扩展机制为外部用户定制组件埋点提供了极大的便利。下面以 Thrift 扩展,具体分析如何实现一个组件埋点。
PS : Thrift 是外部用户基于 SOFATracer API 扩展实现的,目前仅用于其公司内部使用,SOFATracer 官方组件中暂不支持,请知悉;后续会沟通做者提供 PR ,在此先表示感谢。
这里咱们以 Thrift RPC 插件实现为例,分析如何实现一个埋点插件。
一、实例工程的分包结构
从上图插件的工程的包结构能够看出,整个插件实现比较简单,代码量很少,但从类的定义来看,直观的体现了SOFATracer 插件埋点机制所介绍的套路。下面将进行详细的分析与介绍。
二、实现 Tracer 实例
RpcThriftTracer 继承了 AbstractTracer 类,是对 clientTracer、serverTracer 的扩展。
PS:如何肯定一个组件是 client 端仍是 server 端呢?就是看当前组件是请求的发起方仍是请求的接受方,若是是请求发起方则通常是 client 端,若是是请求接收方则是 server 端。那么对于 RPC 来讲,便是请求的发起方也是请求的接受方,所以这里实现了 AbstractTracer 类。
三、扩展点类实现
PS:上面表格中 SpanEncoder 和 AbstractSofaTracerStatisticReporter 的实现中,多了一层AbstractRpcDigestSpanJsonEncoder 和AbstractRpcStatJsonReporter的抽象,主要是因为 client 和 server 端有公共的逻辑处理,为了减小冗余代码,而采用了多继承模式处理。
四、数据传播格式实现
SOFATracer 支持使用 OpenTracing 的内建格式进行上下文传播。
五、Thrift Rpc 自身扩展点之请求拦截埋点
咱们内部 Thrift 支持 SPI Filter 机制,所以要实现对请求的拦截过滤,示例插件埋点的实现就是基于 SPI Filter 机制完成的。其中 FilterThriftBase 抽象也是为了便于处理 consumerFilter 和 providerFilter 公共的逻辑抽象。
对于一个组件来讲,一次处理过程通常是产生一个 Span;这个 Span 的生命周期是从接收到请求到返回响应这段过程。
可是这里须要考虑的问题是如何与上下游链路关联起来呢?在 Opentracing 规范中,能够在 Tracer 中 extract 出一个跨进程传递的 SpanContext 。而后经过这个 SpanContext 所携带的信息将当前节点关联到整个 Tracer 链路中去,固然有提取(extract)就会有对应的注入(inject);更多请参考 蚂蚁金服分布式链路跟踪组件链路透传原理与SLF4J MDC的扩展能力分析 | 剖析 。
链路的构建通常是 client-server-client-server 这种模式的,那这里就很清楚了,就是会在 client 端进行注入(inject),而后再 server 端进行提取(extract),反复进行,而后一直传递下去。
在拿到 SpanContext 以后,此时当前的 Span 就能够关联到这条链路中了,那么剩余的事情就是收集当前组件的一些数据;整个过程大概分为如下几个阶段:
从请求中提取 spanContext
构建 Span,并将当前 Span 存入当前 tracer上下文中(SofaTraceContext.push(Span))
设置一些信息到 Span 中
返回响应
Span 结束&上报
下面结合 SOFATracer 自身 API 源码来逐一分析下这几个过程。
Thrift 插件中的 Consumer 和 Provider 分别对应于 client 和 server 端存在的,因此在 client 端就是将当前请求线程的产生的 traceId 相关信息 Inject 到 SpanContext,server 端从请求中 extract 出 spanContext,来还本来次请求线程的上下文。
相关处理逻辑在FilterThriftBase抽象类中,以下图:
inject 实现代码
extract 实现代码
serverReceive 这个方法是在 AbstractTracer 类中提供了实现,子类不须要关注这个。在 SOFATracer 中也是将请求大体分为如下几个过程:
客户端发送请求 clientSend cs
服务端接受请求 serverReceive sr
服务端返回结果 serverSend ss
客户端接受结果 clientReceive cr
不管是哪一个插件,在请求处理周期内均可以从上述几个阶段中找到对应的处理方法。所以,SOFATracer 对这几个阶段处理进行了封装。见下图:
这四个阶段实际上会产生两个 Span,第一个 Span 的起点是 cs,到 cr 结束;第二个 Span 是从 sr 开始,到 ss 结束。
clientSend
serverReceive
...
serverSend
clientReceive 复制代码
来看下 Thrift Rpc 插件中 Consumer 和 Provider 的实现
ConsumerTracerFilter
红色框内对应的客户端发送请求,也就是 cs 阶段,会产生一个 Span。
ProviderTracerFilter
服务端接收请求 sr 阶段,产生了一个 Span 。上面appendProviderRequestSpanTags这段代码是为当前这个 Span 设置一些基本信息,包括当前应用的应用名、当前请求的 service、当前请求的请求方法以及请求大小等。
在 Filter 链执行结束以后,ConsumerTracerFilter(见图一)和 ProviderTracerFilter(见图二) 分别在 finally 块中又补充了当前请求响应结果的一些信息到 Span 中去。而后分别调用 clientReceive 和 serverSend 结束当前 Span。
图一
图二
关于 clientReceive 和 serverSend 里面调用 Span.finish 这个方法( opentracing 规范中,Span.finish 的执行标志着一个 Span 的结束(见图一),当调用finish执行逻辑时同时会进行span数据的上报(见图二)和当前请求线程MDC资源的清理操做(见图三)等。
图一:
当前 Span 数据上报,代码以下:
图二:
清理当前请求线程的 MDC 资源的一些逻辑处理等,代码以下:
图三:
上述以自定义 Thrift RPC 插件为例,分析了下 SOFATracer 插件埋点实现的一些细节。前面不只总结了编写插件的基本埋点思路并且还对 SOFATracer 自身 API 实现作了相应的分析。基于此本节则从总体思路上来总结如何编写一个 SOFATracer 的插件:
一、肯定所要实现的插件,理解该组件的使用场景和扩展点,而后肯定以哪一种方式来埋点,好比:是 Filter or Interceptor
二、实现当前插件的 Tracer 实例,这里需明确当前插件是以 client 存在仍是以 server 存在
三、实现一个枚举类,用来描述当前组件的日志名称和滚动策略 key 值等
四、实现插件摘要日志的 Encoder ,实现当前组件的定制化输出
五、实现插件的统计日志 Reporter 实现类,经过继承 AbstractSofaTracerStatisticReporter 类并重写 doReportStat
六、定义当前插件的传播格式
七、要明确咱们须要收集哪些数据
本文经过对 SOFATracer 插件的埋点机制进行分析介绍,并结合自定义 Thrift RPC 插件的埋点实现进行了分析。但愿经过本文可以让更多的同窗理解基于 SOFATracer 自身 API 的埋点实现,能根据自身须要实现本身的插件。
《Dapper,大规模分布式系统的跟踪系统》:
欢迎加入互动钉钉群,搜索群号:23127468 便可加入。
长按关注,获取分布式架构干货
欢迎你们共同打造 SOFAStack https://github.com/alipay