乐趣区

关于开源:终于SOFATracer-完成了它的链路可视化之旅

📄

文|赵陈(SOFA 开源之夏链路项目组 )

武汉理工大学计算机工程硕士在读

钻研方向:唐卡线稿的主动上色

校对|宋国磊(SOFATracer commiter)

本文 6971 字 浏览 18 分钟

背 景

有幸参加开源软件供应链点亮打算——暑期 2021 反对的开源我的项目,目前 SOFATracer 曾经可能将埋点数据上报到 Zipkin 中,本我的项目的次要指标是将产生的埋点数据上报给 Jaeger 和 SkyWalking 中进行可视化展现。

PART. 1 SOFATracer

SOFATracer 是蚂蚁团体基于 OpenTracing 标准开发的分布式链路跟踪零碎,其核心理念就是通过一个全局的 TraceId 将散布在各个服务节点上的同一次申请串联起来。通过对立的 TraceId 将调用链路中的各种网络调用状况以日志的形式记录下来,以达到透视化网络调用的目标,这些链路数据可用于故障的疾速发现,服务治理等。

SOFATracer 提供了异步落地磁盘的日志打印能力和将链路跟踪数据上报到开源产品 Zipkin 做分布式链路跟踪展现的能力。这次加入开源之夏流动的工作是要把链路跟踪数据上报到 Jaeger 和 SkyWalking 中进行展现。

SOFATracer 数据上报

上图是 SOFATracer 中的链路上报流程,Span#finish 是 span 生命周期的最初一个执行办法,这是整个数据上报的入口,SOFATracer 的 report span 办法中含有上报链路展现端和日志落盘两个局部。SOFATracer 中没有把上报数据采集器和日志落盘离开只是在日志落盘之前调用 SOFATracer#invokeReporListeners 办法,找到零碎中所有实现了 SpanReportListener 接口并退出了 SpanReportListenersHolder 的实例,调用其 onSpanReport 办法实现链路数据上报至数据采集器。上面的代码片段是 invokeReportListeners 办法的具体实现。

 protected void invokeReportListeners(SofaTracerSpan sofaTracerSpan) {
    List<SpanReportListener> listeners = SpanReportListenerHolder
        .getSpanReportListenersHolder();
    if (listeners != null && listeners.size() > 0) {for (SpanReportListener listener : listeners) {listener.onSpanReport(sofaTracerSpan);
        }
    }
}

SpanReportListenerHolder 中的实例在我的项目启动的时候退出,且分为 Spring Boot 利用和 Spring 利用两种状况:

  • 在 Spring Boot 利用中主动配置类 SOFATracerSpanRemoteReporter 会将以后所有 SpanReportListener 类型的 bean 实例保留到 SpanReportListenerHolder 的 List 对象中。SpanReportListener 的实例对象会在各自的 AutoConfiguration 主动配置类中注入到 IOC 容器中。
  • 在 Spring 利用中通过实现 Spring 提供的 bean 生命周期接口 InitializingBean,在 afterPropertiesSet 办法中实例化 SpanReportListener 的实例对象并且退出到 SpanReportListenerHolder 中。

要实现把 SOFATracer 中的 trace 数据上传到 Jaeger 和 SkyWalking 须要实现 SpanReportListener 接口并在利用启动的时候把对应实例退出到 SpanReportListenersHolder 中。

PART. 2 Jaeger 数据上报

下图是 Jaeger 中数据上报的局部图示,图中 CommandQueue 中寄存的是刷新或增加指令,生产者是采样器和 flush 定时器,消费者是队列处理器。采样器判断一个 span 须要上报后向 CommandQueue 中增加一个 AppendCommand,flush 定时器依据设置的 flushInterval 一直向队列中增加 FlushCommand,队列处理器一直从 CommandQueue 中读取指令判断是 AppendCommand 还是 FlushCommand,如果刷新指令把以后 byteBuffer 中的数据发送到承受端,如果是增加指令把这个 span 增加到 byteBuffer 中暂存。

在实现上报到 Jaeger 过程中次要工作是 Jaeger Span 和 SOFATracer Span 模型的转换,转换过后利用下面的逻辑发送 span 到后端。

上图是 Jaeger 中 Sender 的 UML 图,从图中能够看到有两种类型的 Sender 别离是 HTTPSender 和 UDPSender。别离对利用 HTTP 发送数据和 UDP 发送数据,在实现 SOFATracer 上报 Jaeger 中应用 UDPSender 发送 span 数据到 Jaeger Agent 中,应用 HTTPSender 间接发送数据到 Jaeger-Collector 中。

Jaeger Span 与 SOFATracer Span 模型的转换

模型转换对照

TraceId 和 SpanId 的解决

TraceId 的转换:

  • 问题在 SOFATracer 中的 TracerId 的产生规定是:服务器 IP + ID 产生的工夫 + 自增序列 + 以后过程号

例如:0ad1348f1403169275002100356696 前 8 位 0ad1348f 即产生 TraceId 的机器的 IP,这是一个十六进制的数字,每两位代表 IP 中的一段,咱们把这个数字,按每两位转成 10 进制即可失去常见的 IP 地址示意形式 10.209.52.143,您也能够依据这个法则来查找到申请通过的第一个服务器。前面的 13 位 1403169275002 是产生 TraceId 的工夫。之后的 4 位 1003 是一个自增的序列,从 1000 涨到 9000,达到 9000 后回到 1000 再开始往上涨。最初的 5 位 56696 是以后的过程 ID,为了避免单机多过程呈现 TraceId 抵触的状况,所以在 TraceId 开端增加了以后的过程 ID。——TraceId 和 SpanId 生成规定

在 SOFATracer 中 TraceId 是 String 类型,然而在 Jaeger 中 TraceId 是应用的两个 Long 型的整数来形成最终的 TraceId。

解决方案

在 Jaeger 中示意 TraceId 的是 TraceIdHigh 与 TraceIdLow 在外部再应用函数将两者转换成 String 类型的 TraceIdAsString 在拼接的过程中别离将两个 ID 转换为对应的 HexString,当 HexString 不够 16 位时头部加 0。

    StringBuilder builder = new StringBuilder(desiredLength);
    int offset = desiredLength - id.length();

    for (int i = 0; i < offset; i++)
        builder.append('0');
    builder.append(id);
    return builder.toString();}

SpanId 的转化

  • 问题在 Jaeger 中 SpanId 是 Long 型整数,在 SOFATracer 中是 String 类型。
  • 解决办法这个问题的解决办法同之前已有的转化为 Zipkin 中的 SpanId 的解决办法一样,也是应用 FNV Hash 将 String 映射成抵触较小的 Long 型。

两种上传形式

配合 Jaeger Agent

The Jaeger agent is a network daemon that listens for spans sent over UDP, which it batches and sends to the Collector. It is designed to be deployed to all hosts as an infrastructure component. The agent abstracts the routing and discovery of the Collectors away from the client.

Jaeger Agent 被设计成一种根本组件部署到主机上,可能将路由和发现 Collector 的工作从 client 中抽离进去。Agent 只能承受通过 UDP 发送的 Thrift 格局的数据,所以要应用 Jaeger Agent 须要应用 UDPSender。

应用 HTTP 协定上报 Collector

当应用 UDP 上报到 Jaeger Agent 的时候为了保证数据不在传输过程中失落应该把 Jaeger Agent 部署在服务所在的机器,然而有的状况不能满足前述要求,这时能够应用 HTTP 协定间接发送数据到 Collector,这时应用 HTTPSender。

PART. 3 SkyWalking 数据上报

SkyWalking 是分布式系统的应用程序性能监督工具,专为微服务、云原生架构和基于容器架构而设计,提供分布式追踪、服务网格遥测剖析、度量聚合和可视化的一体化解决方案。SkyWalking 采纳字节码注入的形式实现代码的无侵入,且性能体现优良。SkyWalking 的 receiver-trace 模块能够通过 gRPC 和 HTTPRestful 服务承受 SkyWalking 格局的 trace 数据,在实现上报 SkyWalking 中抉择的上报形式是通过 HTTPRestful 服务上报。

模型转换对照

SegmentId、SpanId、PatentSpanID 的转换

SOFATracer 中的 SpanId 是一个字符串,然而在 SkyWalking 中 SpanId 和 ParentSpanId 是一个 int 整数并且每一个 segment 中的 SpanId 都是从 0 开始编号,SpanId 最大值由配置的一个 segment 中最多有多少 span 指定。在转换过程中须要指定 SpanId,因为当初每一个 segment 中只有一个 span,所以转换生成的 segment 中的 span 的 ID 能够固定成 0。

SegmentId 是用来惟一标识一个 segment 的,如果 segmentId 雷同前一个 segment 会被前面的 segment 笼罩导致 span 失落。最初应用的 segmentId 的结构形式是 segmentId = traceId + SpanId 哈希值 + 0/1,其中 0 和 1 别离代表 server 和 client。最初须要加上 client 和 server 的起因是在 Dubbo 和 SOFARPC 中存在 server -> server 的状况,其中 RPC 调用的 client、server span 的 SpanId 和 parentId 都一样,须要以此来辨别它们,否则 client 端的 span 会被笼罩。

Dubbo 与 SOFARPC 的解决

根本的模型是 client-server-client-server-. 这种模式,然而在 Dubbo 和 SOFARPC 中存在 server -> server 的状况,其中 client span、server span 两个 span 除了 kind 类型不同之外,其余的信息是一样。

  • parentSegmentId

要找出 parentSegmentId,在非 SOFARPC 和 Dubbo 状况下,遵循 server -> client,client -> server 也就是 client 的父 spa 只能是 server 类型的,server 类型的父 span 只能为空或 client 类型。转换形式是在 SOFARPC 和 Dubbo 中,依据应用 SkyWalking Java Agent 上报时两者的链路展现状况,转化依照:

server span:parentSegmentId = traceId + parentId 哈希值 + client(1)

client span:parentSegmentId = traceId + parentId 哈希值 + server(0)

server span:parentSegmentId = traceId + spanId 哈希值 + client(1)

client span:parentSegmentId = traceId + parentId 哈希值 + server(0)

  • 字段和 networkAddressUsedAtPeer 字段 :

Peer 字段

在 Dubbo 中 Peer 字段能够通过 remote.host、remote.port 两个 tag 组成 SOFARPC 中在 remote.ip 中蕴含了 IP 和 port,只应用 IP,因为在 server 端上报的 span 中无奈取得 client 应用的是本人的哪个端。

networkAddressUsedAtPeerDubbo

能够通过 local.host、local.port 组成 SOFARPC 中不能间接从 span 中获取到本机的 IP,应用的是获取本机的第一个无效 IPv4 地址,然而没有端口号,所以在下面的 peer 字段中也只用了 IP。

### 展现拓扑图

在构建链路的过程中几个比拟要害的字段是 peer、networkAddressUsedAtPeer、parentService、parentServiceInstance、parentEndpoint。其中 Peer 和 networkAddressUsedAtPeer 别离示意对端地址以及 client 端调用以后实例应用的地址,这两个字段的作用是将链路中的实例连接起来,如果这两个字段缺失会导致链路断开,在转换过程中这两个字段通过在 span 的 tag 中寻找或获取本机第一个非法的 IPv4 地址取得。后三个字段的作用是指出对应的父实例节点,如果不设置这三个字段会产生一个空的实例信息,如下图所示。目前 SOFATracer 中在能在上下文中流传的只有 TraceIdSpanId、parentId、sysBaggage、bizBaggage 从其中无奈失去以上的三个字段,为了能展现拓扑图在 SOFATracer 的上下文中减少了七个字段 service、serviceInstance、endpoint、parentService、parentServiceInstance、parentEndpoint、peer 这样就可能在转换的过程中取得父服务的相干信息。

异步上传

应用 HTTP 上报 Json 格局的 segment 数据到后端,上报时以 message 为单位,多个 segment 组合成一个 message。

流程如下图,span 完结后将转换好的 segment 退出到 segment 缓冲数组中,另一个线程一直到数组中刷新数据到 message,当 message 的大小达到最大值或期待发送的工夫达到设定值就发送一次数据,设置的 message 最大默认为 2MB。

PART. 4 压 测

测试配置

  • Windows 10
  • Memory 16G
  • Disk 500GB SSD
  • Intel(R) Core(TM) i7-7700HQ CPU @2.80GHz 2.80GHz

测试形式

部署一个蕴含六个服务的调用链路。设置三组对照:

  • 不采集 span
  • 50% 采集
  • 全量采集

Jaeger 测试后果

测试中相干的几个参数设置如下:

Jaeger Agent 形式

全量采集

50% 采集

不采集

上报 Jaeger Collector

全量采集

50% 采集

不采集

SkyWalking 测试后果

选集采集

50% 采集

不采集

测试小结

在全采样时三种上报形式中上报 SkyWalking 的本机吞吐率是最低的只有 512.75/sec,相比于上报 Jaeger Agent 吞吐率降落了约 14%,相比于上传 Jaeger Agent 吞吐率缩小了 11.89%。就每种形式比照全采样与不采样时吞吐率的变动:上报 Jaeger Agent 时因为全采样吞吐率降落了 14.6%,上报 Jaeger Collector 时因为全采样吞吐率降落了 17%,上报 SkyWalking 时因为全采样吞吐率降落了约 23%。

本次介绍的 SOFATracer 的链路可视化,将会在下个版本 release。

「播种」

很侥幸可能加入这次的开源之夏流动,在浏览 SOFATracer 源码的过程中学习了很多优良的设计思维与实现形式,实现的过程中会去模拟一些源码的实现形式在这个过程中本人学习到了很多。在我的项目施行过程中也发现了本人的一些问题,比方在解决问题时有一点思路就开始做,没有深挖这个思路是否可行,这个坏习惯节约了许多工夫。这是我第一次参加到开源社区的相干流动中,在这个过程中理解了开源社区的运作形式,在当前的学习过程中会更加努力提高本人的代码能力,争取能为开源社区做出一点奉献。

特别感谢感激宋国磊老师对我的急躁领导,在我的项目过程中宋老师帮忙我解开了很多纳闷,学到很多货色,感激 SOFAStack 社区在整个过程中对我的诸多帮忙,感激流动主办方提供的平台。

「参考资料」

  1. 蚂蚁团体分布式链路跟踪组件 SOFATracer 数据上报机制和源码剖析 | 分析
  2. 应用 SkyWalking 实现全链路监控
  3. Zipkin-SkyWalking Exporter
  4. STAM:针对大型分布式应用零碎的拓扑自动检测办法

本周举荐浏览

  • 攀登规模化的顶峰 – 蚂蚁团体大规模 Sigma 集群 ApiServer 优化实际
  • SOFAJRaft 在同程游览中的实际
  • 蚂蚁团体技术危险代码化平台实际(MaaS)
  • 下一个 Kubernetes 前沿:多集群治理

退出移动版