[转帖]终于!SOFATracer 完成了它的链路可视化之旅
https://my.oschina.net/sofastack/blog/5283439
▼
背 景
有幸参与开源软件供应链点亮计划——暑期 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 社区在整个过程中对我的诸多帮助,感谢活动主办方提供的平台。
「参考资料」
蚂蚁集团分布式链路跟踪组件 SOFATracer 数据上报机制和源码分析 | 剖析
使用 SkyWalking 实现全链路监控
Zipkin-SkyWalking Exporter
STAM:针对大型分布式应用系统的拓扑自动检测方法
本周推荐阅读
[转帖]终于!SOFATracer 完成了它的链路可视化之旅的更多相关文章
- MonkeyRunner测试一MonkeyRunner的使用
最近搭建MonkeyRunner开发环境,安装PyDev时,饱受折磨,现在终于搞定.因为一些原因,用了JDK1.6,在线安装插件PyDev成功后,Windows-Preferences里找不到PyDe ...
- React Native填坑之旅--与Native通信之iOS篇
终于开始新一篇的填坑之旅了.RN厉害的一个地方就是RN可以和Native组件通信.这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是.自定义视图的使 ...
- 罗佳琪的第三次预备作业——虚拟机的安装及Linux的初步学习
虚拟机的安装及Linux的初步学习 坎坷的安装过程 首先我按照老师给的基于VirtualBox虚拟机安装Ubuntu图文教程进行了下载,下载很顺利但是安装时出现了问题. 起初我以为是电脑位数问题,但我 ...
- [转帖]国产闪存颗粒终于熬出头 紫光存储S100固态硬盘评测
国产闪存颗粒终于熬出头 紫光存储S100固态硬盘评测 https://www.cnbeta.com/articles/tech/830875.htm 全国产的 SSD 群联貌似是对岸的 不过不管怎么说 ...
- [转帖]程序员:我终于知道post和get的区别
程序员:我终于知道post和get的区别 置顶 2019-11-14 00:03:09 zhanglinblog 阅读数 15316 文章标签: post和get的区别程序员 更多 分类专栏: .ne ...
- [转帖]鲁大师Q3季度PC处理器排行:AMD、Intel终于五五开了
鲁大师Q3季度PC处理器排行:AMD.Intel终于五五开了 https://www.cnbeta.com/articles/tech/902375.htm 近日,鲁大师发布了Q3季度PC处理器排行. ...
- 全球第一本基于Bootstrap V3.x的图书《深入理解Bootstrap》终于上市了,再次免费送书15本【活动结束】
先说活动规则,再说书的事 经过将近1年的努力,终于有了第一本自己独立编写的书:<深入理解Bootstrap>,基于最新版V 3.1 ,侧重于源码详解.架构分析.插件扩展(全新开发)实战.为 ...
- arcengine 开发经典帖
http://bbs.esrichina-bj.cn/ESRI/viewthread.php?tid=25575&page=1&extra= 使用ArcGIS Engine 开发自定义 ...
- [转帖]为应用程序池“XXX”提供服务的进程在与 Windows Process Activation Service 通信时出现严重错误。该进程 ID 为“XXXX”。数据字段包含错误号。
[终极解决方案]为应用程序池“XXX”提供服务的进程在与 Windows Process Activation Service 通信时出现严重错误.该进程 ID 为“XXXX”.数据字段包含错误号. ...
- ACM菜鸡退役帖——ACM究竟给了我什么?
这个ACM退役帖,诸多原因(一言难尽...),终于决定在我大三下学期开始的时候写出来.下面说两个重要的原因. 其一是觉得菜鸡的ACM之旅没人会看的,但是新学期开始了,总结一下,只为了更好的出发吧. 其 ...
随机推荐
- 玩转Python:处理音频文件,两个非常重要的库,很实用,附代码
pyaudio和sounddevice都是用于Python中音频处理和流的库,允许用户通过他们的API录制.播放和处理音频数据.下面是对这两个库的简要介绍: PyAudio PyAudio 提供了 P ...
- Python笔记三之闭包与装饰器
本文首发于公众号:Hunter后端 原文链接:Python笔记三之闭包与装饰器 这一篇笔记介绍 Python 里面的装饰器. 在介绍装饰器前,首先提出这样一个需求,我想统计某个函数的执行时间,假设这个 ...
- grafana_mysql安装
https://dl.grafana.com/oss/release/grafana-5.4.0-1.x86_64.rpm #官网下载安装包 [root@zbx4_0 source]# rpm -iv ...
- 【华为云技术分享】40%性能提升,华为云推出PostgreSQL 12 商用版
摘要:日前,华为云数据库正式推出了RDS for PostgreSQL 12版本,并开始商用.本文将从华为云RDS for PostgreSQL 12的4大特性和架构图等多方面来解读华为云Postgr ...
- ECS实践案例丨逻辑卷的创建和扩容操作指导
摘要:实现跨硬盘使用,在传统硬盘之上的一层,在云服务器中可以实现跨EVS使用,用户在某些场景需要创建逻辑卷或者对已有的逻辑卷进行扩容处理,或者在某些时候由于误操作导致上述操作失败. [背景描述]: 实 ...
- Mysql开发实践:error while loading shared libraries: libaio解决方案
摘要:Mysql出现问题:error while loading shared libraries: libaio解决方案. 本文分享自华为云社区<Mysql出现问题:error while l ...
- Java的这个强大功能,很多人都不知道
摘要:大多数框架采用单一的语言所开发.JNI这项Java中提供的强大功能,却逐渐的被人遗忘了. 本文分享自华为云社区<Java中一个逐渐被遗忘的强大功能,强到你难以置信!!>,作者:冰 河 ...
- 还在用 Excel 和 SQL?火山引擎 VeDI 这款产品帮你更快处理数据
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,并进入官方交流群 对大多数职场打工人来说,看数据.用数据一直是项有"门槛"的工作. 特别是在企业业务快速发展的背景下,为 ...
- 无法获得数据库 'model' 上的排他锁。请稍后重试该操作
标题: Microsoft SQL Server Management Studio 数据库 "XXXX" 的 创建 失败. (Microsoft.SqlServer.Smo) 有 ...
- 灵魂拷问std::enable_shared_from_this,揭秘实现原理
参考博客: std::enable_shared_from_this原理浅析 引言 在C++编程中,使用智能指针是一种安全管理对象生命周期的方式.std::shared_ptr是一种允许多个指针共享对 ...