项目地址:https://github.com/EasyTelemetry/easytelemetry-java-opentelemetry
引言
在现代分布式系统开发中,可观测性早已不是“锦上添花”,而是保障系统稳定与性能的“生命线”。OpenTelemetry 作为业界的统一标准,在链路追踪、指标和日志收集方面表现出色。但你是否遇到过这样的场景:即使看到了慢请求的 Span,却依然无法定位到具体是哪行代码拖慢了速度?这时,你可能会想到 Java 诊断神器 Arthas 的 trace 命令。它虽好,用起来却总有些“隔靴搔痒”的感觉。今天,我们就来聊聊 EasyTelemetry 这个创新方案,看看它是如何巧妙融合 OpenTelemetry 与 Arthas Trace 能力,一劳永逸地解决这些痛点的。
Arthas Trace 命令的优势与挑战
Arthas 的 trace 命令确实是排查线上性能问题的利器,它能动态追踪方法内部调用栈,精准统计每一行代码的耗时。但在实际的生产环境中,它常常面临“理想很丰满,现实很骨感”的窘境,核心挑战有三点:
-
并发请求过滤困难
当多个用户请求同时命中同一个方法时,trace 命令的输出会混杂在一起,从这些海量数据中筛选出你关心的那个特定请求的轨迹,无异于大海捞针。
-
资源消耗显著
trace 命令在执行期间,会对目标方法进行字节码增强,这会引入不小的 CPU 和内存开销。在线上高并发、高负载的场景下,这种开销可能会影响服务的稳定性,让人不敢轻易使用。
-
数据孤岛问题
trace 的结果通常以文本形式输出在 Arthas 控制台,这些宝贵的数据孤立存在,无法与调用链上下游的 OpenTelemetry 链路数据关联起来。这意味着你无法进行端到端的全链路问题定位,排查效率大打折扣。
EasyTelemetry 的终极目标
面对上述挑战,EasyTelemetry 应运而生,它设定了三个非常明确且务实的目标:
- ✅ 链路数据集成:将指定方法的细粒度
trace 结果,无缝嵌入到 OpenTelemetry 的标准链路(Span)数据中,在一个视图里就能看到宏观链路与微观代码执行轨迹。
- ✅ 极致轻量化:确保采集到的代码轨迹数据体积极小,目标每行代码仅需 1~4 字节的存储开销,最大程度减少对网络和存储的压力。
- ✅ 超低资源消耗:将采集过程带来的额外性能损耗,严格控制在目标方法本身执行时间的 1% 以内,实现近乎无感的监控体验,让你可以放心地在生产环境使用。
技术实现:EasyTelemetry 核心原理
EasyTelemetry 是基于 OpenTelemetry 的二次开发项目,完全兼容其原生生态。它通过以下几项核心技术,实现了高效、精准的代码轨迹采集:
1. 字节码增强技术
利用 Java Agent 机制和 ASM 字节码操作库,在目标方法执行的关键位置(如方法入口、各行代码间、出口等)动态插入轻量级的埋点逻辑。这些埋点会实时捕获代码执行轨迹,包括行号、执行次数、累计耗时等核心信息。
2. 高效编码压缩
原始的性能数据如果直接记录会很庞大。EasyTelemetry 通过自研的算法,将这些轨迹数据压缩编码成一段非常紧凑的十六进制字符串,大幅降低了数据的存储与传输成本。
3. 链路数据注入
编码后的轨迹数据,会被作为一个自定义标签(Tag)注入到当前 OpenTelemetry 的 Span 中。随后,这个携带了详细代码轨迹的 Span 会随着正常的链路数据一同上报到后端的观测平台(如 Jaeger、Zipkin 等)。
4. 可视化解码工具
光有数据不行,还得能看懂。项目提供了配套的解码器,能够将上报的十六进制字符串还原成清晰、可读的格式化报告,直观展示每一行代码的执行耗时与占比。
示例演示
原始代码片段
假设我们有一个 Spring MVC 的控制器方法,如下所示:
@RequiresPermissions(value={"system:user:edit"})
@GetMapping(value={"/authRole/{userId}"})
public String authRole(@PathVariable(value="userId") Long userId, ModelMap mmap) {
/*251*/ this.userService.checkUserDataScope(userId);
/*252*/ SysUser user = this.userService.selectUserById(userId);
/*254*/ List roles = this.roleService.selectRolesByUserId(userId);
/*255*/ mmap.put("user", user);
/*256*/ mmap.put("roles", SysUser.isAdmin((Long)userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
return this.prefix + "/authRole";
}
配置规则
通过一个简单的 JSON 文件来定义你需要监控的方法:
{
"trace": [
{
"rootSpanName": "GET /system/user/authRole/{userId}",
"javaMethodDesc": "com.ruoyi.web.controller.system.SysUserController#authRole(java.lang.Long,org.springframework.ui.ModelMap)",
"traceTagKey": "ruoyi_authRole",
"triggerOptimizeTimes": 10
}
]
}
rootSpanName: 匹配 OpenTelemetry 中的 Span 名称。
javaMethodDesc: 指定要追踪的完整方法签名。
traceTagKey: 自定义标签的 Key,轨迹数据会存储在这个 Key 下。
triggerOptimizeTimes: 触发次数,用于控制采样频率,优化性能。
轨迹数据样例
当请求执行后,该方法内部的代码执行轨迹会被压缩编码,并附加到对应的 Span 上。在 Jaeger 等 UI 中,你可以看到类似下面的标签:
ruoyi_authRole: 00FB010120138DF7FAAA80105518B01A2700103400
这段短短的十六进制字符串,就包含了方法内所有关键行的执行信息。
解码可视化效果
通过 EasyTelemetry 提供的解码工具,将上述字符串解码后,我们可以得到一份清晰的性能报告:
总耗时:50.05ms
行号 执行次数 耗时: 耗时占比
251 1 0.85ms, 1.70%
252 1 22.24ms, 44.44%
254 1 25.99ms, 51.93%
255 1 0.00ms
256 1 0.52ms, 1.04%
257 1 0.00ms
报告一目了然地告诉我们:第 254 行的 selectRolesByUserId 方法是本次请求最主要的耗时点(占 51.93%),这为性能优化提供了最直接的依据。
总结与展望
EasyTelemetry 创造性地将 Arthas Trace 的代码级诊断能力,与 OpenTelemetry 标准化、平台化的链路追踪体系相结合。它不仅解决了传统诊断工具在并发过滤、资源消耗和数据孤岛方面的核心痛点,更以一种轻量、优雅的方式,将代码执行细节融入到了整个可观测性体系中。
对于开发者而言,这意味着在排查复杂问题时,不再需要在不同工具间反复切换、人工拼接信息。在像云栈社区这样的技术交流平台,分享和复现问题也变得更为高效直观。未来,该项目计划进一步优化采集算法、扩展多语言支持,并与更多主流 APM 平台深度集成,持续助力开发者构建高可观测、高可靠的分布式系统。