Arthas 在线诊断工具:从安全 Attach 到生产止损
一次不能重启的延迟现场
订单接口的 P99 从 180 ms 升到 1.8 s,CPU 只有 35%,日志没有异常,发布包也声称没有变。此时最危险的动作不是“查不到”,而是为了尽快看到现场,对整个服务包执行无限次 watch 或 trace:Arthas 会通过 Instrumentation 对匹配类做字节码增强,热方法每次调用还要计算条件、组织对象并输出结果。诊断命令本身可能成为新的延迟来源。
先写下一个可证伪假设:“OrderService#submit 的慢调用集中在某个一级下游,并且只发生在特定租户。”随后保存实例、PID、JVM 运行用户、应用版本、QPS、错误率、P99、CPU、GC 和线程数。停止条件也在 Attach 前确定,例如错误率持续上升、P99 相对基线明显恶化或实例健康检查失败;生产阈值来自该服务 SLO 和演练数据,不照抄文章里的演示数值。
Arthas 适合短时回答运行中 JVM 的类、参数、返回值、调用耗时和线程状态。它不是长期 APM,也不替代日志、指标、分布式 Trace 或压测。Attach 获得的是进程内诊断能力,权限等级接近目标 JVM 本身,必须按生产变更管理。
先在本地把退出动作练熟
准备 JDK 和 curl,用目标 JVM 的同一操作系统用户执行。启动器和演示程序进入一次性目录;arthas-boot.jar 首次运行还会把发行包下载到 ~/.arthas/lib/<版本>/arthas/,Server 日志默认写入 ~/logs/arthas/。这两处不属于 LAB_DIR,后面的清理必须分别确认:
LAB_DIR="$(mktemp -d "${TMPDIR:-/tmp}/arthas-lab.XXXXXX")"
chmod 700 "$LAB_DIR"
curl -fL https://arthas.aliyun.com/arthas-boot.jar -o "$LAB_DIR/arthas-boot.jar"
curl -fL https://arthas.aliyun.com/math-game.jar -o "$LAB_DIR/math-game.jar"
java -jar "$LAB_DIR/math-game.jar" >"$LAB_DIR/math-game.log" 2>&1 &
DEMO_PID=$!
printf 'lab=%s pid=%s\n' "$LAB_DIR" "$DEMO_PID"
java -jar "$LAB_DIR/arthas-boot.jar" "$DEMO_PID"交互列表出现多个进程时不要凭序号猜测;回到另一个终端,用 jps -l、ps -o user,pid,args -p "$DEMO_PID" 和启动日志交叉确认。Linux 上 Attach 通常要求相同 UID;受控 sudo 的形式是 sudo -u <jvm-user> -H ...。容器里还要区分宿主 PID 与容器 PID namespace,并确认目标用户能访问临时目录。一次失败不能成为给业务容器长期增加 root 或 --privileged 的理由。
进入 Arthas 后先执行低风险识别:
version
jvm
dashboard -n 1
thread -n 3
sc -d demo.MathGame
sm demo.MathGame primeFactors成功证据不是出现提示符,而是 PID、主类、JDK、ClassLoader hash 和方法签名都与目标一致。sc -d 若显示同名类由多个 ClassLoader 加载,后续命令用 -c <hash> 锁定;增强错误副本会得到“命令成功但业务无命中”的假象。
正向实验:从一条慢调用下钻
先只观察一次、一个类,并只展开一个层级:
watch demo.MathGame primeFactors '{params[0],returnObj,throwExp}' -x 1 -n 1 -m 1预期先看到 Affect(class count: 1, method count: 1),随后只有一次 AtExit 或 AtExceptionExit。-m 1 限制匹配类数,-n 1 限制命中次数,-x 1 限制对象展开深度。-b 观察方法进入点,此时没有返回值和异常;退出点的 params 可能已经被方法修改,不能拿它冒充原始入参。
把观察收窄到慢调用,避免每次调用都计算和打印:
watch demo.MathGame primeFactors '{params[0],throwExp}' '#cost>10' -x 1 -n 3 -m 1
trace demo.MathGame run '#cost>10' -n 3 -m 1
monitor -c 5 -n 2 demo.MathGame primeFactorswatch 的 OGNL 条件在调用现场求值,字段访问越深、对象越大,CPU 与敏感数据风险越高。trace 展示目标方法内部的一级调用节点,不是跨服务 Trace,也不会自动递归整棵调用树;发现可疑下级后,把下一次入口移到该方法。monitor 按周期汇总调用次数、成功/失败、平均 RT 与失败率,适合先判断问题是否持续,但平均值会掩盖长尾,仍要回到有条件的单次证据。
一次合理结论应形如:“在相同流量窗口,3 次超过 10 ms 的 run 中,一级调用 primeFactors 占主要现场耗时;应用 P99 在命令停止后回到原基线。”它不能写成“primeFactors 导致全链路 90% 延迟”,因为 trace 没有覆盖网络排队、下游服务和代理层。
反向实验:让错误证据稳定出现
仍在本地演示进程中,对同一个已确认的方法故意放大对象展开深度和输出次数。命令仍以单类、单方法和 20 次命中为硬上限,因此可以稳定看到错误做法的成本,又不会变成无限监听:
watch demo.MathGame primeFactors '{params,target,returnObj,throwExp}' -x 4 -n 20 -m 1预期仍是 Affect(class count: 1, method count: 1),随后最多输出 20 次并自动结束;它不会因为 -m 1 被拒绝,因为演示程序只匹配一个类。与前面的 -x 1 -n 1 对照,终端输出量、对象遍历深度和暴露字段明显增加,这才是可验证的风险证据。-m 只限制匹配类数,不限制方法数、调用频率、对象大小或单次序列化成本,不能把它误当总开销保险丝。
不等待 20 次也可以按 Ctrl+C 停止当前监听器,随后立即执行:
jobs
reset demo.MathGameCtrl+C 停止当前监听器,reset 才负责恢复 Arthas 增强过的类。若命令以 & 放到后台,断开会话后任务仍可能存在,应使用 jobs 找到任务并 kill <job-id>。HTTP API 执行持续输出命令时还必须设置服务端执行超时;只有 -n 1 而方法始终不命中,任务仍会等待。
tt 保存的是对象引用
偶发异常难以在屏幕前等到时,可以有界记录一次现场:
tt -t demo.MathGame primeFactors 'throwExp != null' -n 1 -m 1
tt -l
tt -i <上一步实际返回的INDEX>
tt --delete-alltt 把参数、返回值、异常等放进进程内的 Map<Integer, TimeFragment>。官方默认容量为 100,退出客户端不会自动清空;记录持有对象引用,可能延长大对象和敏感对象生命周期。tt --delete-all 后再次 tt -l,空列表才是清理证据。
tt -i <index> -p 会在当前生产进程重放真实方法。它可能再次扣款、写库、发消息或调用下游,默认列入禁用命令。即便方法看起来只读,也要证明幂等性、外部副作用和事务语义,不能把线上重放当调试快捷键。
反编译只能回答运行字节码
sc -d demo.MathGame
jad -c <实际ClassLoader-hash> --source-only demo.MathGame primeFactorsjad 能核对 JVM 中已加载实现与发布预期是否一致。它无法还原注释、原始局部变量名和全部泛型信息,编译器生成结构也可能改变外观。多 ClassLoader 应先选择 hash;否则反编译到错误副本,会把类加载问题误判成发布问题。
生产 Attach 的网络与认证边界
Arthas Server 默认监听 127.0.0.1,telnet 端口通常为 3658,HTTP/Web Console 端口通常为 8563。最终值读取启动输出、session 与当前配置。生产保持回环地址,通过堡垒机或临时 SSH 端口转发访问,不把监听地址改成 0.0.0.0。
arthas.username=diag
arthas.password=<由密钥系统写入的临时强密码>
arthas.localConnectionNonAuth=false完整发行包从 Arthas 目录读取 arthas.properties;启动参数、环境、系统属性和该文件存在优先级,实际生效值要从启动日志和未认证连接测试验证。凭证文件权限设为 0600,由密钥系统临时写入,诊断结束后销毁。不要把密码放在命令行参数、脚本、URL 或工单里:命令行可能被进程列表和审计系统记录。HTTP API 使用 Basic Auth 时必须位于 TLS 或可信加密隧道内;Base64 不是加密。配置 arthas.localConnectionNonAuth=true 表示即使设置密码,本地连接仍可免认证;共享宿主机或不可信同租户环境应改为 false 并验证本地未认证命令被拒绝。
Tunnel 让 Agent 主动连接 Tunnel Server,再以 agentId 寻址。--tunnel-server 改变上行地址,--agent-id 改变实例标识;固定、可猜的 ID 不能充当凭证。Tunnel Server 自身必须位于受控网络之后,补齐身份认证、细粒度授权、TLS、来源白名单和审计,不能直接使用公开测试地址承载生产诊断。Web Console、HTTP API 和 MCP 都可以触发强诊断命令,应按同一高权限入口治理。
开销为什么会突然放大
watch、trace、monitor、tt 的共同链路是:类模式匹配、Instrumentation 重转换、监听器进入热方法、条件求值、结果序列化与终端输出。匹配类数决定增强面,热方法 QPS 决定回调频率,OGNL 和 -x 决定对象遍历成本,-n 与超时决定持续时间。四者必须一起受控。
生产先在一台低流量实例运行 15 至 30 秒,由第二个人同时观察错误率、P99、CPU、GC 和健康检查。看到 Affect 数量超过审批值、输出速度失控或业务指标跨过停止线,立即停止监听器并 reset。不要同时增强整个集群;逐实例证据一致后,通常应转向 JFR、指标或可控压测,而不是把在线增强变成常驻方案。
Arthas 内置 profiler 使用 async-profiler。CPU 高选 cpu;RT 高而 CPU 不高选 wall 并区分线程;分配压力选 alloc;锁竞争选 lock。采样事件、容器权限、符号和火焰图解释应按 async-profiler 的证据模型处理,不能把 profiler 图与 trace 方法耗时混为一谈。
停止、恢复与销毁证据
按下面顺序退出,避免把“终端关了”误当“现场恢复了”:
jobs
profiler status
profiler stop
tt --delete-all
reset
stop没有运行 profiler 时,profiler stop 可能提示未启动,这是可接受的状态证据。quit/exit 只断开当前客户端,Arthas Server 和端口仍在;stop 才关闭 Server,并会重置普通增强类。redefine 与 retransform 属于代码变更能力,生产默认禁用;一旦使用,不能假设 reset 或 stop 能还原原字节码,必须按变更记录重新加载已知制品或重启实例。
本地实验最后在外部终端完成进程和一次性目录清理:
kill "$DEMO_PID" 2>/dev/null || true
wait "$DEMO_PID" 2>/dev/null || true
case "$LAB_DIR" in
"${TMPDIR:-/tmp}"/arthas-lab.*) rm -rf -- "$LAB_DIR" ;;
*) printf 'refuse to delete unexpected path: %s\n' "$LAB_DIR" >&2 ;;
esacLAB_DIR 删除后,先确认当前用户没有 Arthas Server,再盘点启动器额外写入的位置。发行包可以作为受控缓存保留,后续以版本目录和文件摘要复用;共享构建机或一次性演练机若要求无残留,只删除本次确认下载的版本目录,不删除整个 ~/.arthas,也不使用来源不明的变量拼接递归路径:
jps -l | grep -E 'arthas|ArthasBootstrap' || true
find "$HOME/.arthas/lib" -maxdepth 2 -mindepth 1 -type d -print 2>/dev/null
find "$HOME/logs/arthas" -maxdepth 1 -type f -print 2>/dev/null
printf 'type the exact version directory shown above: ' >&2
IFS= read -r ARTHAS_VERSION
case "$ARTHAS_VERSION" in
''|*[!0-9.]*)
printf 'refuse invalid version: %s\n' "$ARTHAS_VERSION" >&2
exit 1
;;
esac
ARTHAS_VERSION_DIR="$HOME/.arthas/lib/$ARTHAS_VERSION"
case "$ARTHAS_VERSION_DIR" in
"$HOME"/.arthas/lib/[0-9]* )
test -d "$ARTHAS_VERSION_DIR/arthas" || {
printf 'not an Arthas release directory: %s\n' "$ARTHAS_VERSION_DIR" >&2
exit 1
}
rm -rf -- "$ARTHAS_VERSION_DIR"
;;
* ) printf 'refuse to delete unexpected path: %s\n' "$ARTHAS_VERSION_DIR" >&2 ;;
esac~/logs/arthas/ 可能同时保存其他诊断会话,不能整目录删除。先按事件时间窗和文件内容确认归属,将需要留存的日志转入权限为 0700 的事件目录并登记摘要;其余文件按组织保留策略逐个删除。生产环境通常保留已登记的发行包以避免故障时临时联网,日志则按诊断数据处理,因为其中可能包含类名、路径、命令参数和业务对象。无论保留还是销毁,都要记录版本、所有者、最后使用时间和处理结果。
生产输出写入按事件创建的 0700 目录,文件默认 0600,名称包含事件号、实例和时间窗,不复用固定 /tmp/result.html。命令历史、异步任务、arthas-output、反编译结果、火焰图和 TT 数据都可能包含类名、SQL、路径、令牌和用户数据。产物要有 owner、访问组、哈希、保留期限和销毁记录,禁止进入公开工单或普通群聊。
把一次救火变成团队能力
项目运行手册维护允许包前缀、关键 ClassLoader、敏感字段、命令模板、禁止命令、默认 -m/-n/-x、最长时间、停止阈值和恢复顺序。应用 owner 判断方法语义与副作用,平台 owner 管理版本、哈希、分发和端口基线,安全 owner 审批 Attach 身份与产物,当班工程师记录命令和业务指标。
容量治理不能只算工具二进制大小。在线增强消耗目标实例 CPU 与延迟预算,tt 消耗堆,异步输出消耗磁盘,Tunnel 与 Web 入口消耗网络和安全运维成本,产物还带来存储与合规成本。团队每次升级都在代表性压测环境复跑“空闲基线、单命令、恢复后”三段 A/B;若恢复后指标不回到同负载基线,升级不能进入生产。
最终诊断记录应同时回答:为什么选这个实例和方法,命令实际匹配多少类与方法,采集期间业务指标怎样变化,获得了什么可反驳证据,何时停止,怎样证明增强、任务、端口与敏感产物已经清理。缺少其中任何一项,都只能算一次临时操作,不能沉淀为可复用手册。
