原文链接:一文看懂分布式链路监控系统
Dapper
Google 2010 年发布了 Dapper,提出分布式链路追踪技术的两个基本要求:
- 拥有广泛的覆盖面
- 提供持续的监控服务
分布式链路监控系统的设计目标:
- 应用级透明
- 低开销:对原服务影响足够小,网络传输和数据存储的消耗少
- 扩展性和开放性:支撑常用中间件,并支持特殊场景的定制化
1. 数据模型
OpenTracing 规范的数据模型包含三种:
- Trace: 一整条调用链,包括跨进程、跨线程的所有 segment
- Segment: 标识一个进程(JVM)或线程 内所有操作的集合,即包含多个 Span
- Span: 标识一个具体的操作,在不同的实现中有不同的划分方式
- Entry Span: 入栈Span,是 segment的入口。如 http 或 rpc 的入口
- Local Span: 记录本地方法的调用
- Exit Span: 出栈Span,是 segment 的出口
一次用户请求的调用链路如下图,各个公司的监控指标中可能也有对应的定义,概念上应该是一致的。
上图中每一次请求都会有唯一的traceId,不同的监控系统的唯一键的设计算法不一致,但是都需要保证唯一性且包含足够的请求信息。例如阿里 Eagleeye 的做法:
从图中可以看出,Span是真正串联整个链路的单元,系统可以通过若干个Span串联整个调用。那么怎么描述一个Span树?阿里的 Eagleeye 设计了一套 rpcId 的定义机制,如下图所示,层级和次序可以分别用分段数量和每一段的递增数字来表示。
在实际的跨进程传输时,只需要携带 traceId 和 rpcId,每个 segment 中的数据分段收集上报。上报的过程会有乱序和丢失,乱序可以通过 rpcId 还原真实的请求链路,丢失的话会缺省,使用子节点代替
2. 数据埋点
埋点的要求:应用级同名,低开销。Eagleege 和 SkyWalking 分别采用了不同的方案
方案1 编码
阿里 Eagleeye 采用的埋点方式,通过中间件预留的扩展点实现
选用这种方式的原因:
- 公司内部使用,中间件较为统一
- 直接编码的维护成本更低,性能消耗更低
方案2 字节码增强
字节码增强的两种方式包含 Attach 和 Agent.
Attach
实现机制:利用 JVM 提供的 attach api,实现一个 JVM 对另一个运行中的 JVM 的通信。可以灵活的对正在运行的 JVM 进行字节码修改。一个示意图如下:
Arthas 就是使用 attach 方式实现的线上debug.
Agent
Javaagent 在启动时增加 agent 的启动参数,指定需要挂载的 agent。
实现机制:在目标 JVM 执行 main 之前,执行 agent 的 premain(通过启动参数指定路径),通过字节码插入前置的处理代码。
agent 方式在修改字节码方面更为灵活,甚至可以修改 JDK 的核心类库。
Skywalking
Skywalking 等开源方案则会面临更加复杂的环境,因此不能采用直接编码的方式。Skywalking 采用的开发模式:
- core: skywalking 提供核心的字节码增强能力,以及拓展接口
- 中间件基于官方或社区的插件植入自身的应用,如果没有现有的插件,可以自己开发
Skywalking 采用 Byte Buddy 的字节码增强类库,兼顾 高性能、易用、功能强大等特性。
插件模型:
- 增强哪个类的哪个方法?
- 提供了 ClassMatch,支持自定义组合匹配规则
- 怎么变更调用逻辑?
- 支持自定义方法拦截器,包括前置、后置、异常等多个时机的拓展
- 增强的前置条件?
- 部分场景下增强插件的版本可能不兼容,可以使用 witness 机制确保期望的类或方法存在,才会增强
Skywalking Agent的整体模型如下图:
- SPI:skywalking 暴露的插件规范接口,开发者基于这些接口实现插件
- Plugins:基于 SPI 实现的各类插件
- CORE:加载插件并利用 Byte Buddy 提供的字节码增强逻辑对应用中的指定类和方法的字节码进行增强
3. 数据收集
Trace 的数据收集一般是先存本地,再异步收集。
Eagleeye 采用「并发环形队列」的方案:
- 读指针:指定队列中最后一条数据
- 写指针:指定下一个存放的位置
- 当写的速度超过读的速度,可以根据策略丢弃或者覆盖老数据
Skywalking 采用分区的 QueueBuffer 来存储 trace 数据。多个消费线程通过 driver 平均分配到各个 QueueBuffer 上进行消费。QueueBuffer 的两种实现机制:
- JDK 阻塞队列:常用于服务端
- 普通数组+原子下标:更轻量级,性能更高。常用在 agent
传输流程如下图:
- Eagleeye: 数据先存入本地,再通过 agent 将数据采集到服务端
- Skywalking: 提供了 GRPC 和 Kafka 两种方式