Java

Arthas 线上排障时,我最常先敲的不是 6 个命令,而是两条确认路径

Arthas 真正好用的地方,不是背会 6 个命令,而是在线上先判断自己现在要确认的是线程在忙什么,还是某个方法为什么慢。把 `dashboard`、`thread`、`trace`、`watch`、`stack`、`jad` 放回两条真实排障路径里,命令边界会清楚很多。

  • Arthas
  • Java
  • 线上排障
  • 性能排查
  • 工具链
15 分钟阅读

很多人学 Arthas,都是从命令表开始记:

  • dashboard 看全局
  • thread 看线程
  • trace 看耗时
  • watch 看参数
  • stack 看调用来源
  • jad 看线上代码

这些都没错,但真到线上现场,问题往往不在于你记不记得这 6 个名字,而在于你不知道自己现在到底在确认哪一种怀疑。

于是现场很容易变成这样:

  • dashboard
  • thread -n 5
  • 然后忍不住对几个方法都 trace
  • 觉得返回值也可疑,再开 watch
  • 最后材料很多,判断还是没收住

我后来慢慢发现,Arthas 真正值钱的地方,不是“有 6 个高频命令”,而是它能把原本散落在不同工具里的确认动作,压进同一个交互现场里。

但前提是:不要把它当命令大全来用,要把它当排障路径里的取证工具来用。

所以这篇不再按 6 个命令逐个铺开,而是只讲我在线上更常走的两条路径:

  1. 服务突然慢了,但我还不知道慢在全局压力、线程等待,还是某个业务方法
  2. 代码行为和预期不一样,我怀疑不是“性能问题”,而是参数、调用来源或线上版本本身有偏差

那 6 个命令都会出现,但它们出现的顺序,会更像真实现场。

一、第一条路径:接口突然变慢,我先判断“线程在忙什么”,再判断“方法为什么慢”

这条线是我用 Arthas 最多的场景。

告警通常长这样:

  • 某个服务 RT 抬高
  • 错误率没明显爆
  • CPU 也许高,也许不高
  • 但值班群里已经开始问是不是 Java 进程卡住了

这时候如果一上来就 trace 某个 Service 方法,通常有点早。

因为你还没回答一个更基础的问题:

现在到底是整机 / JVM 已经很紧,还是某些线程卡住了,还是业务方法本身真的慢?

1. 我会先用 dashboard 看一眼全局,不是为了找根因,而是为了防止方向跑偏

dashboard

我最关心的不是把屏幕上每一块都解释一遍,而是先用它排掉几种很粗的误判:

  • heap、GC 看起来是不是已经明显不对
  • 线程数有没有异常抬高
  • system load 是不是已经很夸张
  • top threads 里能不能直接看出热点线程

dashboard 在这里的价值很像“先抬头看病人是不是已经全身发热”,而不是直接判断病因。

如果这一步已经能看出 JVM 整体在高压,比如线程数抬得很厉害、load 也高,那我后面会更警惕线程堆积、锁等待、外部调用卡住这些方向。

如果这一步看起来并不夸张,那说明问题也许更集中在少数链路,而不是整个 JVM 都在喘。

2. 接下来我通常直接去 thread,因为线上很多“慢”最后都是线程先把真相露出来

thread -n 5

这一步比很多人想的更关键。

因为值班现场里,大家说的“慢”常常是不同东西:

  • 有的是 CPU 真高
  • 有的是线程卡在锁上
  • 有的是线程在等数据库
  • 有的是线程在等下游 HTTP
  • 还有的是线程并不忙,只是请求都排在池子里

thread 最值钱的地方,就是它会逼你先回答一句很朴素的话:

这些线程现在到底是在跑,还是在等?

如果我看到高 CPU 线程一直停在 JSON 序列化、对象转换或者某段循环里,那后面很可能会沿热点方法继续收。

如果我看到一批线程都停在 JDBC、连接池或某个 RPC client 上,那我通常不会急着把矛头指向 Java 代码本身,因为这更像外部等待把请求时间拖长了。

如果我看到线程栈高度一致,反复卡在同一段业务调用上,那就说明 trace 的目标已经差不多有了。

这也是我为什么很少把 thread 当成“CPU 高时才用”的命令。在线上,它更像是我判断“慢的形态”的第一把刀。

3. 只有当可疑方法已经收得比较窄时,我才会上 trace

trace com.example.OrderService createOrder

很多人用 Arthas 时最容易犯的错,就是把 trace 当成雷达乱扫。

trace 真正值钱的前提,是你已经有了一个相对具体的怀疑:

  • 我怀疑慢点就在这个 Service 方法里
  • 或者我已经知道线程老停在这个 DAO / client 调用附近
  • 我现在想确认到底是方法内部哪一段在吃时间

它回答的是:

这段调用链里,耗时到底沉在谁身上?

我更常见到两种结果。

第一种,是 trace 很快把锅收窄到下游调用上,比如数据库查询或某个 RPC client。这种情况下,Arthas 已经帮我完成了工作的一半:至少我知道不用继续在本地业务分支里翻来翻去。

第二种,是 trace 发现真正慢的是本地逻辑,比如组装大对象、循环处理、聚合计算,或者某个缓存 miss 后的回源分支。这种时候,继续沿代码看就有意义了。

但无论是哪一种,我都会提醒自己:trace 给的是耗时结构,不自动等于根因。

比如数据库调用慢,可能是 SQL 真慢,也可能是连接池排队;本地方法慢,也可能只是被上游放大的结果。它帮你把怀疑压到一层,但不会替你做最后的归因。

4. 如果我发现“慢”并不稳定,而是怀疑某类参数才会触发坏路径,这时才轮到 watch

watch com.example.OrderService createOrder '{params,returnObj,throwExp}' -x 2

watch 不是我在性能现场第一时间就会开的命令。

因为很多性能问题,先知道参数长什么样,并不能马上帮助你判断方向。

但当现场出现下面这几种迹象时,watch 就很有价值:

  • 不是所有请求都慢,只有某一类请求慢
  • 同一个方法平时正常,偶尔会走到很奇怪的分支
  • 你怀疑开关值、配置值、入参形态和你想的不一样
  • 你怀疑异常被吞了,或者返回值其实已经走了兜底

这一步更像是拿实样,不是看趋势。

我会尽量只看最关键的几个字段,不让线上输出变成一面瀑布墙。watch 当然强,但它最容易把人带进“既然能打印,那就多看一点”的冲动里。真到高压现场,克制比功能更重要。

5. 这条路径里,stackjad 往往不是第一批出场,但经常负责最后的坐实

如果 tracewatch 之后,我还是有两个典型疑问,Arthas 还有后手。

一个疑问是:

这个方法到底是谁调进来的?

这时我会用:

stack com.example.OrderService createOrder

它特别适合处理那种“我知道这个方法有问题,但我不确定是谁把它带进主链路”的场景。

真实现场里,这种误判特别常见。你以为是 Controller 主链路调的,结果其实是异步补偿任务、定时任务或者 MQ 消费者调的。只要调用来源认错,后面的判断就很容易跟着歪。

另一个疑问是:

线上跑的,真的是我脑子里那份代码吗?

这时我才会上:

jad com.example.OrderService

jad 很像最后那一下确认。很多线上排障其实不是技术难,而是你默认了几个前提:

  • 这个版本已经发上去了
  • 这个 if 分支现在一定是这样走的
  • 这个灰度实例跟别的实例是一致的

可只要这些前提里有一个是错的,前面看再多线程、参数、耗时都可能白忙。jad 不是高频乱用的命令,但一旦怀疑“线上代码和本地认知不一致”,它会非常省时间。

二、第二条路径:现象像 bug,不像纯性能问题时,我会先确认“是不是我理解错了现场”

还有一类现场,也很适合 Arthas,但起手式和前一条线不一样。

它的典型描述不是“服务慢了”,而是:

  • 同一段逻辑在测试环境没问题,线上却偶发异常
  • 某个分支明明不该进,线上却进了
  • 某个开关明明应该生效,结果像没生效
  • 返回值看起来不对,但日志又没完整打出来

这类问题如果还按“全局 -> 线程 -> trace”的节奏走,有时会比较慢。因为你真正怀疑的,往往不是耗时,而是现场事实和你的认知有没有对上

1. 我通常先用 watch,先确认参数、返回值和异常是不是我以为的那样

如果我怀疑某种参数组合把代码带进了坏路径,或者返回值和日志描述对不上,watch 往往是最快的。

它解决的不是“哪里最慢”,而是:

  • 线上真实参数到底长什么样
  • 返回值是不是提前兜底了
  • 异常是不是根本没冒到日志那层

这一步一旦确认错了,很多争论会直接结束。

2. 然后我会用 stack 看调用来源,因为线上 bug 经常不是代码不会跑,而是入口和你想的不一样

同一个方法,被 Controller 调、被定时任务调、被 MQ 消费调,现场含义完全不一样。

如果你只看方法体,不看谁调了它,就很容易把“入口错了”误判成“逻辑错了”。

stack 在这种时候特别实用,因为它补的是调用背景。

3. 最后再用 jad 把线上类本身确认掉

我吃过几次亏以后,现在只要现场出现下面任一信号,就会很自然想到 jad

  • 大家都说“代码明明改过了”
  • 某个逻辑只在一部分实例上表现异常
  • 配置和源码对不上
  • 你越看越觉得线上行为不像当前分支

这种时候,去争论“按理说”没什么意义,直接看线上类更快。

所以在这条路径里,Arthas 更像一个“事实校验器”:

  • watch 校验输入输出
  • stack 校验调用来源
  • jad 校验线上实现

三、这 6 个命令我怎么记:不是按功能背,而是按问题背

如果非要把这篇里出现的 6 个命令各记一句,我现在更习惯这样记:

  • dashboard:先看现场是不是已经整体失衡
  • thread:先搞清线程到底在跑还是在等
  • trace:可疑方法收窄后,再拆耗时结构
  • watch:怀疑参数、返回值、异常不对时,直接看样本
  • stack:怀疑调用入口认错了时,用它补背景
  • jad:怀疑线上版本或实际实现不对时,用它做最后确认

你会发现,这样记之后,它们就不再是平铺的 6 个功能点,而是两条排障路径里的 6 个证据位置。

四、Arthas 最常见的误用,不是不会敲命令,而是太想一步到位

我自己在线上最常提醒自己的,反而不是某个参数怎么写,而是下面几件事。

1. 不要一接上 Arthas 就把能开的都开一遍

命令多,不代表证据更完整。

如果你还没想清自己现在在确认什么,开得越多,越容易把注意力带散。

2. trace 很强,但前提是目标已经相对明确

它不适合做大范围盲扫,更不适合当“总会告诉我答案”的万能入口。

3. watch 很容易让人上头

线上能看到参数和返回值当然很爽,但如果对象太大、方法太高频,现场很快就会被噪音淹掉。

4. 很多“性能问题”最后不是靠耗时图坐实,而是靠调用来源和线上代码确认掉

这也是为什么 stackjad 看起来不如 trace 那么“炫”,但在真实现场里经常特别省时间。

五、如果你只想先把一套顺序记住,我建议记这一版

我自己比较常用的起手顺序是这样的。

现场像“慢了,但方向不清”

  1. dashboard
  2. thread
  3. 怀疑点收窄后 trace
  4. 只有在参数、异常、开关值可疑时再补 watch
  5. 调用来源不清时补 stack
  6. 怀疑版本或实现不一致时补 jad

现场像“行为不对,不像纯性能问题”

  1. watch
  2. stack
  3. jad
  4. 如果过程中发现其实是方法耗时异常,再回头补 trace

Arthas 真正好用的地方,不是你背下来“6 个最有用的命令”,而是你能更快分清:

  • 我现在是在找慢点
  • 还是在找线程状态
  • 还是在确认输入输出
  • 还是在确认是谁调进来的
  • 还是在确认线上到底跑了什么代码

当这件事分清之后,命令本身就没那么难记了。