Arthas 5 分钟速成:我在生产环境用 trace + watch 把 Spring Boot 接口慢调用拆解到方法级 Arthas 5 分钟速成我在生产环境用 trace watch 把 Spring Boot 接口慢调用拆解到方法级上周有个线上接口的 P99 延迟突然从 120ms 飙到 1.8s日志里只打印了 “Request processed”耗时信息一概没有。灰度环境复现不了本地更别想了——数据量和并发根本不是一回事。拆了半天最后是靠 Arthas 的trace和watch两个命令在不重启、不侵入代码的前提下把接口内部的调用链路一层层剥开定位到了那个看起来人畜无害的 Redis 批量操作。这篇是写给那些知道 Arthas 但觉得用不起来的同学目标是复制粘贴就能跑5 分钟出结果。场景不是代码没改而是依赖变胖了出问题的接口是订单查询逻辑本身几个月没动过。但最近上游接了个新功能传进来的订单 ID 从平均 3 个涨到了 80 个。代码里有个循环for(LongorderId:orderIds){OrderDetaildetailredisTemplate.opsForValue().get(order:orderId);// ...}80 次串行 Redis 调用网络往返累积起来1.8s 就这么来的。问题是本地调试根本不可能发现这个——80 个 ID 的测试数据没人构造。加日志可以但改代码、打包、发版快的话半小时慢的话半天。期间线上继续报错。Arthas 的价值就在这里.attach 一个进程实时观察方法入参和耗时看完detach零代码改动。2 分钟安装和 attachArthas 是 Alibaba 开源的 Java 诊断工具不需要改代码不需要重启服务。下载在目标服务器执行curl-Ohttps://arthas.aliyun.com/arthas-boot.jarjava-jararthas-boot.jar执行后会列出当前所有 Java 进程输入对应序号即可 attach。比如[INFO]arthas-boot version:3.7.2[INFO]Found existingjavaprocess, please choose one and input the serial number... *[1]:12345/opt/app/order-service.jar[2]:12346/opt/app/payment-service.jar1attach 成功后会进入 Arthas 的命令行界面arthasPID接下来就可以开始诊断了。补充.detach 的正确姿势诊断完成后不要直接关终端先用stop命令优雅退出stop# 退出当前 Arthas 会话恢复被增强的类如果直接用CtrlC或关窗口Arthas 的增强instrument可能残留在 JVM 里导致轻微性能损耗。stop会清理所有插桩完全恢复原状。先用 trace把接口耗时拆到方法级trace的作用是跟踪方法调用链路输出每个层级的耗时精确到毫秒。trace com.example.order.service.OrderQueryService getOrderDetails#cost100-n5参数说明com.example.order.service.OrderQueryService getOrderDetails类名 方法名#cost100过滤条件只显示耗时超过 100ms 的调用-n 5只打印 5 次避免刷屏输出示例---ts2026-06-0614:32:10;thread_namehttp-nio-8080-exec-5;id36;---[1768.234ms]com.example.order.service.OrderQueryService:getOrderDetails()---[12.341ms]com.example.order.service.OrderQueryService:validateParams()---[1.234ms]com.example.order.service.OrderQueryService:buildCacheKey()---[1734.567ms]com.example.order.service.OrderQueryService:fetchFromRedis()# 就是这|---[1732.123ms]org.springframework.data.redis.core.ValueOperations:get()---[15.678ms]com.example.order.service.OrderQueryService:assembleResponse()---[0.234ms]com.example.order.service.OrderQueryService:logMetrics()一眼就能看出来fetchFromRedis()占了 1734ms占总耗时的 98%。但 trace 只能告诉你哪个方法慢具体是哪一行、哪个入参导致的需要watch出场。进阶trace 的层数控制如果方法调用层级很深可以用-j参数限制深度避免输出爆炸trace com.example.order.service.OrderQueryService getOrderDetails#cost100-n5-j3-j 3表示只展开 3 层调用适合深层业务链路的快速定位。再用 watch锁定罪魁祸首入参watch用来观察方法的入参和返回值可以精确定位数据层面的问题。watchcom.example.order.service.OrderQueryService fetchFromRedis{params,returnObj,throwExp}#cost100-n5-x2参数说明{params,returnObj,throwExp}输出表达式打印参数、返回值、异常#cost100只观察耗时超过 100ms 的调用-n 5打印 5 次-x 2展开深度 2避免对象太长刷屏输出示例methodcom.example.order.service.OrderQueryService.fetchFromRedislocationAtExit ArrayList[Object[][ArrayList[Long[12345], Long[12346], Long[12347], Long[12348], Long[12349], //... 一共80个元素],],]returnObjArrayList[size80]看到size80的一瞬间问题就确认了。正常情况下这个参数只有 3-5 个这次是 80 个。每一个订单 ID 都触发一次 Redis 串行 get80 次网络往返不慢才怪。进阶watch 的条件表达式watch 支持 OGNL 表达式可以精确过滤。比如只看入参列表长度大于 50 的调用watchcom.example.order.service.OrderQueryService fetchFromRedis{params[0].size,returnObj.size}params[0].size 50-n10这个条件比单纯用#cost100更精准能直接锁定数据量异常的场景。组合诊断trace watch 的实战套路在生产环境我通常按这个顺序排查Step 1trace 定位慢方法trace com.example.order.service.OrderQueryService getOrderDetails#cost100-n5找到耗时大头的方法比如fetchFromRedis。Step 2watch 观察入参特征watchcom.example.order.service.OrderQueryService fetchFromRedis{params[0].size,returnObj.size}#cost100-n10这里我只观察params[0].size入参列表长度和returnObj.size返回结果长度确认是不是数据量突变。Step 3用 tt 做回放可选Arthas 的ttTimeTunnel可以记录方法调用然后本地回放不需要重新触发线上请求# 记录调用tt-tcom.example.order.service.OrderQueryService getOrderDetails-n3# 查看记录列表tt-l# 选择某条记录在本地重放不触发线上请求tt-i1000-p这个在调试复杂逻辑时非常有用可以避免反复触发线上请求。修复把 80 次串行改成 1 次批量定位到问题后修复方案很简单——把opsForValue().get()改成opsForValue().multiGet()// 之前80 次串行每次 RTT ~20ms累计 1600ms// for (...) redisTemplate.opsForValue().get(key);// 之后1 次批量 getRTT 仍是 ~20msListStringkeysorderIds.stream().map(id-order:id).collect(Collectors.toList());ListOrderDetaildetailsredisTemplate.opsForValue().multiGet(keys);改完上线P99 从 1.8s 回到 95ms。整个过程从 attach Arthas 到定位根因不到 5 分钟。踩坑记录Arthas 生产环境使用的 3 个注意事项1. 不要在高峰期做全量 tracetrace会对目标方法做字节码增强instrument虽然开销很小但在高并发场景下如果同时 trace 多个高频方法还是会产生可观测的 CPU 开销。建议优先 trace 入口方法Controller/Service而不是底层工具类用-n限制打印次数缩短诊断窗口避开业务流量峰值时段2. watch 的输出可能包含敏感信息watch会打印方法入参和返回值如果参数里包含用户手机号、身份证号、密钥等敏感数据输出会明文落在终端日志里。建议用表达式精确控制输出字段不要打印整个对象诊断完成后及时清理终端历史记录在受控环境如堡垒机执行避免信息泄露3. 某些类可能无法被增强如果目标类是接口实现且由 Spring AOP/CGLIB 代理生成trace 可能需要定位到实际实现类而不是接口。例如# 如果 trace 接口不生效尝试 trace 实现类trace com.example.order.service.impl.OrderQueryServiceImpl getOrderDetails#cost100几个常用命令速查表命令作用示例trace方法耗时拆解trace com.example.Service methodName #cost100watch观察入参/返回值watch com.example.Service methodName {params,returnObj}tt记录和回放方法调用tt -t com.example.Service methodNamejvm查看 JVM 信息jvmthread线程状态诊断thread -n 5CPU 占用最高的 5 个线程heapdump生成堆转储heapdump /tmp/dump.hprofprofiler火焰图采样profiler start --event cpu写在最后很多工程师遇到线上性能问题时第一反应是加日志、改代码、重新发版。这个流程在预发布环境没问题但在生产环境就是时间杀手。Arthas 的核心价值不是它有多少命令而是它让诊断变成一件不需要部署的事。attach 上去trace 一下watch 一下看完detach整个过程零侵入、零重启。如果你还没用过建议下次遇到本地复现不了、线上只能瞪眼的场景时花 2 分钟 attach 试试。大概率你会回来感谢这篇文的。附Arthas 官方文档https://arthas.aliyun.com/doc/