一文看懂分布式链路监控系统

302 阅读5分钟

原文链接:一文看懂分布式链路监控系统

Dapper

Google 2010 年发布了 Dapper,提出分布式链路追踪技术的两个基本要求:

  • 拥有广泛的覆盖面
  • 提供持续的监控服务

分布式链路监控系统的设计目标:

  • 应用级透明
  • 低开销:对原服务影响足够小,网络传输和数据存储的消耗少
  • 扩展性和开放性:支撑常用中间件,并支持特殊场景的定制化

1. 数据模型

OpenTracing 规范的数据模型包含三种:

  • Trace: 一整条调用链,包括跨进程、跨线程的所有 segment
  • Segment: 标识一个进程(JVM)或线程 内所有操作的集合,即包含多个 Span
  • Span: 标识一个具体的操作,在不同的实现中有不同的划分方式
    • Entry Span: 入栈Span,是 segment的入口。如 http 或 rpc 的入口
    • Local Span: 记录本地方法的调用
    • Exit Span: 出栈Span,是 segment 的出口

一次用户请求的调用链路如下图,各个公司的监控指标中可能也有对应的定义,概念上应该是一致的。 image.png

上图中每一次请求都会有唯一的traceId,不同的监控系统的唯一键的设计算法不一致,但是都需要保证唯一性且包含足够的请求信息。例如阿里 Eagleeye 的做法: image.png

从图中可以看出,Span是真正串联整个链路的单元,系统可以通过若干个Span串联整个调用。那么怎么描述一个Span树?阿里的 Eagleeye 设计了一套 rpcId 的定义机制,如下图所示,层级和次序可以分别用分段数量和每一段的递增数字来表示。 image.png

在实际的跨进程传输时,只需要携带 traceId 和 rpcId,每个 segment 中的数据分段收集上报。上报的过程会有乱序和丢失,乱序可以通过 rpcId 还原真实的请求链路,丢失的话会缺省,使用子节点代替

2. 数据埋点

埋点的要求:应用级同名,低开销。Eagleege 和 SkyWalking 分别采用了不同的方案

方案1 编码

阿里 Eagleeye 采用的埋点方式,通过中间件预留的扩展点实现

选用这种方式的原因:

  • 公司内部使用,中间件较为统一
  • 直接编码的维护成本更低,性能消耗更低

方案2 字节码增强

字节码增强的两种方式包含 Attach 和 Agent.

Attach

实现机制:利用 JVM 提供的 attach api,实现一个 JVM 对另一个运行中的 JVM 的通信。可以灵活的对正在运行的 JVM 进行字节码修改。一个示意图如下: image.png Arthas 就是使用 attach 方式实现的线上debug.

Agent

Javaagent 在启动时增加 agent 的启动参数,指定需要挂载的 agent。

实现机制:在目标 JVM 执行 main 之前,执行 agent 的 premain(通过启动参数指定路径),通过字节码插入前置的处理代码。 image.png agent 方式在修改字节码方面更为灵活,甚至可以修改 JDK 的核心类库。

Skywalking

Skywalking 等开源方案则会面临更加复杂的环境,因此不能采用直接编码的方式。Skywalking 采用的开发模式:

  • core: skywalking 提供核心的字节码增强能力,以及拓展接口
  • 中间件基于官方或社区的插件植入自身的应用,如果没有现有的插件,可以自己开发 image.png

Skywalking 采用 Byte Buddy 的字节码增强类库,兼顾 高性能、易用、功能强大等特性。

插件模型:

  • 增强哪个类的哪个方法?
    • 提供了 ClassMatch,支持自定义组合匹配规则
  • 怎么变更调用逻辑?
    • 支持自定义方法拦截器,包括前置、后置、异常等多个时机的拓展
  • 增强的前置条件?
    • 部分场景下增强插件的版本可能不兼容,可以使用 witness 机制确保期望的类或方法存在,才会增强

Skywalking Agent的整体模型如下图:

  • SPI:skywalking 暴露的插件规范接口,开发者基于这些接口实现插件
  • Plugins:基于 SPI 实现的各类插件
  • CORE:加载插件并利用 Byte Buddy 提供的字节码增强逻辑对应用中的指定类和方法的字节码进行增强 image.png

3. 数据收集

Trace 的数据收集一般是先存本地,再异步收集。

Eagleeye 采用「并发环形队列」的方案:

  • 读指针:指定队列中最后一条数据
  • 写指针:指定下一个存放的位置
  • 当写的速度超过读的速度,可以根据策略丢弃或者覆盖老数据

image.png

Skywalking 采用分区的 QueueBuffer 来存储 trace 数据。多个消费线程通过 driver 平均分配到各个 QueueBuffer 上进行消费。QueueBuffer 的两种实现机制:

  • JDK 阻塞队列:常用于服务端
  • 普通数组+原子下标:更轻量级,性能更高。常用在 agent

image.png

传输流程如下图:

  • Eagleeye: 数据先存入本地,再通过 agent 将数据采集到服务端
  • Skywalking: 提供了 GRPC 和 Kafka 两种方式

image.png