本文通过对比阿里的EagleEye(鹰眼)与开源项目SkyWalking,从数据模型、数据埋点及数据存储三个核心维度,解析分布式链路监控系统的实现细节,并重点探讨SkyWalking所采用的字节码增强技术方案。
随着业务体量增长,传统的单体架构已难以满足需求。通过将庞大系统拆分为多个相互依赖的子系统并进行独立优化,可以有效提升整体吞吐量。然而,拆分后,完整的业务事务逻辑会分散在多个子系统上。用户的一次点击可能触发跨多个系统的连环调用。如何分析一次请求背后的完整调用路径、如何快速定位导致延迟或故障的具体环节,便成为分布式追踪技术需要解决的核心问题。
以网络搜索为例,阐述链路监控系统面临的挑战。当用户输入关键词后,一个前端服务可能将查询分发给数百个查询服务,每个服务在其索引中搜索。查询还可能被发送至处理敏感词、拼写检查、用户画像分析或寻找图片、视频等特定领域结果的其他子系统。所有服务的返回结果经过选择性组合,最终呈现给用户。这整个过程可能涉及数千台机器和多种不同的服务。
在网络搜索这类对延迟极其敏感的场景中,任何子系统的性能瓶颈都会影响最终用户体验。开发人员仅感知到整体延迟升高,却难以 pinpoint 具体是哪个服务出了问题及其原因。首先,系统中的服务可能随时被新增或修改;其次,在庞大的微服务体系中,开发人员不可能精通每个由不同团队维护的服务;再者,服务和机器资源常被多个应用共享,性能问题可能源于其他应用的行为。
Dapper简介
在分布式追踪领域,Google 早在2010年便发表了关于其内部系统 Dapper 的论文,奠定了许多基础理念。论文中提出了两个基本要求:广泛的覆盖面与持续的监控。基于此,分布式链路监控系统的设计目标可归纳为:
- 应用级透明:监控组件应作为基础组件提供,对应用开发者尽可能透明。在Java中,方法调用是监控的基本单位,实现透明埋点常需对方法进行增强,方式包括硬编码、动态代理、字节码增强等。透明度越高,技术实现难度通常也越大。
- 低开销:监控系统本身必须对业务服务的性能影响降至最低。开销主要体现在方法增强的执行耗时、以及为传输和存储链路数据所产生的额外网络与存储消耗。这就要求产生的追踪数据尽可能精简。
- 扩展性和开放性:作为基础服务,链路监控系统需要支撑企业内成千上万异构的业务系统。它必须具备良好的可扩展性,既能支持常见的框架与中间件,也能方便开发者为特定场景进行定制化开发。
数据模型
OpenTracing规范
Dapper 将一次请求划分为 Trace、Segment、Span 三个层次,此模型已成为 OpenTracing 规范的基础。
- Trace:表示一整条完整的调用链,包含跨进程、跨线程的所有操作。
- Segment:表示单个进程(如一个JVM实例)或线程内的所有操作集合,包含若干个 Span。
- Span:表示一个具体的操作单元。通常可进一步细分:
- Entry Span:入口 Span,是一个 Segment 的起点(如 HTTP/RPC 服务端入口,MQ 消费者入口)。
- Local Span:用于记录本地方法调用。
- Exit Span:出口 Span,是一个 Segment 对外部的调用(如 HTTP/RPC 客户端调用,MQ 生产者,数据库访问)。
一次用户请求的调用链路图示如下:

唯一ID
在海量请求下生成全局唯一且蕴含信息的ID至关重要。EagleEye 的 traceId 设计如下:

该ID能解析出请求时间(2022-10-18 10:10:40)、接收机器IP(11.15.148.83)、进程号(14031)以及服务标识(e,代表Nginx)。末位原子递增数用于防止单机高并发下的ID碰撞。
关系描述
Trace 和 Segment 是逻辑结构,Span 才是串联整个链路的实体。通过模拟方法调用的栈行为,所有 Span 会形成一棵树。EagleEye 设计了 RpcId 来描述这棵树的层次与顺序关系:

RpcId 格式为 0.X1.X2.X3.....Xi,根节点为 0。小数点数量代表层级,最后一位数字代表同级中的顺序。例如 0.1.2 表示第一层的第一个Span下的第二个子Span。给定所有 RpcId,即可还原完整调用链:
- 0
- 0.1
- 0.1.1
- 0.1.2
- 0.1.2.1
- 0.2
- 0.2.1
- 0.3
- 0.3.1
- 0.3.1.1
- 0.3.2
跨进程传输
为了最小化跨进程传输的数据量,每个应用(Segment)独立收集数据。跨进程时通常只携带 traceId 和 rpcId 等极简上下文。服务端收集数据是分段、异步的,可能面临乱序和丢失:

通过 rpcId 可以重建调用树。当某个 Segment 数据缺失时,可用其第一个子节点的上下文进行部分替代和还原。
数据埋点
如何对方法进行增强(埋点)是实现分布式追踪的关键,需同时兼顾应用级透明与低开销。阿里 EagleEye 与开源 SkyWalking 采用了不同的埋点策略。
编码式埋点 (EagleEye)
EagleEye 采用在中间件代码中直接编码埋点的方式,利用中间件预留的扩展点实现。这种方式在阿里内部环境是可行的,因为:
- 有统一的中间件使用规范,覆盖范围有限且可控。
- 有强大的中间件团队统一维护,埋点对上层业务透明。
- 内部应用有强制接入监控的要求,对“可插拔”诉求不强。
直接编码方式在维护性和性能开销上具有优势。
字节码增强 (SkyWalking)
在开源环境中,情况截然不同:
- 技术栈多样,埋点覆盖范围理论上是无限的。
- 中间件由不同组织维护,无法要求其内置埋点代码。
- 开发者没有强制接入监控的约束。
因此,编码式埋点无法满足需求。SkyWalking 采用了插件化字节码增强的模式:

SkyWalking 提供核心增强引擎与扩展接口,通过官方或社区开发的插件来实现对特定框架/中间件的埋点。这种方式提供了极大的灵活性。
字节码增强方式
对 Java 应用进行字节码增强主要有两种方式:
字节码增强库选择
SkyWalking 选择了 Byte Buddy 作为字节码操作库。与 ASM、CGLib、Javassist 等相比,Byte Buddy 在 API 易用性、功能完整性和运行时性能之间取得了良好平衡。下图展示了各库在“生成快速代码”(即增强后方法的执行速度)方面的性能对比(单位:纳秒):

Byte Buddy 在方法调用性能上非常接近原生调用。
SkyWalking 插件模型
SkyWalking 为插件开发者抽象了简洁的接口,主要关注点如下:
- 匹配目标:通过
ClassMatch 定义需要增强的类和方法(支持名称、注解、逻辑组合等)。
- 定义增强逻辑:实现
InstanceMethodsAroundInterceptor 等接口,在方法执行的前、后、异常抛出时插入追踪逻辑。
- 生效条件 (Witness 机制):确保插件只在特定版本或存在的类/方法下生效,避免不兼容的增强。例如,为 Spring MVC 4.x 插件设置见证类。
SkyWalking Agent 核心流程
从 premain 方法开始的主要流程包括:
- 加载所有插件定义。
- 构建 Byte Buddy 的
AgentBuilder,并注册一个关键的 Transformer。
这个 Transformer 在类加载时被 JVM 回调,其核心工作是:根据当前加载的类信息,查找对应的插件,并调用插件的 define 方法进行字节码增强。增强过程会检查 Witness 条件,然后分别对静态方法、实例方法和构造方法插入拦截逻辑。
以实例方法增强为例,最终会在目标方法前后插入对 InstMethodsInter.intercept() 的调用。该类作为桥梁,负责加载并执行用户定义的拦截器 (InstanceMethodsAroundInterceptor),从而完成追踪数据的收集(如创建 Span、记录耗时、传递上下文等)。
数据收集与传输
收集到的 Trace 数据需要发送到后端服务器。为了不影响主业务链路,通常采用异步、缓冲的机制。
存储缓冲
- EagleEye:使用并发环形队列。通过读指针 (
take) 和写指针 (put) 实现无锁的高性能生产-消费模型。当生产速度过快时,可配置丢弃策略。
- SkyWalking:采用分区队列缓冲区 (
QueueBuffer)。在 Agent 端,其实现是一个普通对象数组搭配一个高效的原子下标生成器,以实现轻量级的并发访问。
关于原子下标生成器的性能优化:SkyWalking 曾迭代多个版本以实现线程安全且高性能的数组下标生成。一种有趣的实现是使用 AtomicIntegerArray 的固定偏移位进行操作,旨在利用 CPU 缓存行优化。简单的原子数取模操作虽然在某些测试中性能更高,但原子数会无限增长。目前 SkyWalking 采用的是一种在并发冲突和性能间取得平衡的方案。
数据传输

- SkyWalking:提供 gRPC 和 Kafka 两种方式将 Agent 缓冲区的数据发送到后端 OAP 服务器。
- EagleEye:先将数据写入本地日志文件,再由独立的 Agent 进程采集并上报至服务端。这种方式允许开发人员直接查看服务器上的原始 Trace 日志。
总结
总体而言,SkyWalking 通过插件化字节码增强实现了与业务代码的松耦合,在开源环境下提供了极大的灵活性和可扩展性。而 EagleEye 采用的编码式埋点,依托于阿里内部统一的中间件体系与团队协作,实现了更深度的集成与更低的维护成本。两者都是其所在环境下非常优秀的分布式链路追踪解决方案。
参考链接:
- Dapper 论文
- OpenTracing 规范
- Byte Buddy 官网