arthas诊断工具的使用(案例篇)

586 阅读5分钟

案例一. 修改日志级别

目前存在web服务,日志级别为error,解决线上问题时,需要看info中打印的信息,来排查数据问题。

操作

修改具体某个类的日志级别:

# 查找到具体的类信息
sc -d *VipUserController

# 使用ognl表达式查看日志属性的信息,判断日志级别
ognl -c classLoaderHash "@top.learningwang.arthasdemo.controller.VipUserController@logger"

# 使用ognl表达式修改日志级别
ognl -c classLoaderHash "@top.learningwang.arthasdemo.controller.VipUserController@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)"

# 再次查看日志级别,判断是否修改成功
ognl "@top.learningwang.arthasdemo.controller.VipUserController@logger"

修改全局日志级别:

# 修改root日志级别
ognl -c classLoaderHash '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

用到的命令

scognl

案例二. 更新代码、热加载

线上代码有一行出现了问题,想不重启,修改代码后,实现热加载

操作

# 反编译源码,并保存到文件中
jad --source-only top.learningwang.arthasdemo.controller.VipUserController > /data/VipUserController.java

编辑代码并保存

# 查看类对应的类加载器信息
sc -d top.learningwang.arthasdemo.controller.VipUserController |
grep Hash

# 根据源码,编译出class文件,并保存到执行目录
mc -c classLoaderHash /data/VipUserController.java -d /data/

# 重新加载编译好的class文件
redefine -c classLoaderHash /data/top/learningwang/arthasdemo/controller/VipUserController.class

用到的命令

jadscmcredefine

案例三. 获取apollo配置项

分布式应用使用分布式配置中心apollo,系统运行中,想获取apollo配置项的值,对比apollo配置发布后,程序用的配置项值是否与新配置项一致

操作

第一种方式,通过ognl表达式,调用apollo client提供的java api

ConfigService.getConfig(somePublicNamespace).getProperty(someKey, someDefaultValue)

# 获取apollo配置项:项目访问地址
ognl '@com.ctrip.framework.apollo.ConfigService@getConfig("application").getProperty("server.context-path","")

# 获取apollo配置项:数据库连接地址
ognl '@com.ctrip.framework.apollo.ConfigService@getConfig("datasource").getProperty("spring.datasource.url","")'

第二种方式,apollo配置启动后,会加载到Spring中,通过Spring也可以取出配置值 (这种方式要求代码中要预留工具类,用于获取ApplicationContext对象)

ApplicationContext().getEnvironment().getProperty(someKey)

# 获取apollo配置项:数据库连接地址
ognl '@com.util.SpringContextUtil@applicationContext.getEnvironment().getProperty("spring.datasource.url")'

用到的命令

ognl

案例四. 统计方法执行情况

统计一个线上方法一段时间内的调用情况、执行耗时情况、成功次数、异常次数等

monitor命令可以统计一段周期内方法执行的信息:

监控项含义
timestamp时间戳
classJava类
method方法(构造方法、普通方法)
total调用次数
success成功次数
fail失败次数
rt平均耗时
fail-rate失败率

操作

# 统计方法执行情况,默认60s为统计周期
monitor top.learningwang.arthasdemo.controller.VipUserController helloUser

# 统计方法执行情况,10s为统计周期,统计3次
monitor top.learningwang.arthasdemo.controller.VipUserController helloUser -c 10 -n 3

# 统计方法执行情况,只统计第一个参数长度<5的调用,5s为统计周期
monitor top.learningwang.arthasdemo.controller.VipUserController helloUser "params[0].length<5" -c 5

# 统计方法执行情况,只统计耗时>100ms的调用,5s为统计周期
monitor top.learningwang.arthasdemo.controller.VipUserController helloUser "#cost>100" -c 5

用到的命令

monitor

案例五. 排查死锁问题

线上一个并发执行的方法,长时间未执行完毕,怀疑存在死锁问题。

操作

# thread -b 一键排查死锁
thread -b

# 死锁信息如下
"Thread-57" Id=110 BLOCKED on java.lang.Object@19809eed owned by "Thread-58" Id=111
    at top.learningwang.arthasdemo.service.impl.MockServiceImpl$DeadLock.run(MockServiceImpl.java:82)
    -  blocked on java.lang.Object@19809eed
    -  locked java.lang.Object@7f419718 <---- but blocks 1 other threads!
    at java.lang.Thread.run(Thread.java:748)

用到的命令

thread

案例六. 排查cpu使用率持续较高问题

线上cpu占用持续很高,想排查是哪些线程占用了大量cpu资源。

操作

首先可以用linux命令 top,先排查是不是java进程的问题,是的话,再用arthas排查具体的线程问题

# 展示所有的线程列表
thread
thread-all

# cpu使用率采样修改为2s,默认为200ms
thread -i 2000

# 打印出cpu使用率前5的线程详情,即比较繁忙的线程,cpu使用率采样统计方式请参考官方文档说明
thread -n 5

# 打印id为5的线程详情
thread 5

# 根据状态过滤线程数据(NEW, RUNNABLE, TIMED_WAITING, WAITING, BLOCKED, TERMINATED)
thread --state RUNNABLE
thread |grep RUNNABLE

用到的命令

thread

案例七. 排查方法执行慢的问题

线上一个接口访问比较慢,不知道瓶颈再哪个环节。

操作

通过trace命令追踪方法内部调用路径,并输出方法路径上的每个节点上耗时

# 观测方法内部调用顺序及耗时
trace *MockController mockSlowly

# 观测方法内部调用顺序及耗时,只观测3次
trace *MockController mockSlowly -n 3

# 观测方法内部调用顺序及耗时,包含jdk内部方法
trace *MockController mockSlowly --skipJDKMethod false

# 限制观测范围,第一个参数长度大于10
trace *MockController mockSlowly params[0].length>=10

# 限制观测范围,执行耗时大于100ms
trace *MockController mockSlowly '#cost>100'

 trace命令只会打印出第一层的调用,不会递归打印,想诊断多层的话,可以使用-E加正则表达式的方式,实现trace多个方法。

# trace -E classA|classB methodA|methodB 同时监测多层方法
trace -E top.learningwang.arthasdemo.controller.MockController|top.learningwang.arthasdemo.service.MockService mockSlowly|slowlyQuery

用到的命令

trace

案例八. 观察方法调用情况

线上一个接口,日志打印的不全,不加日志,想观测入参、出参、执行结果、异常信息等。

操作

watch命令可以观测方法执行过程,以及返回结果、异常信息等

# 观测某方法的执行详情,支持ognl表达式输出观测结果
watch *VipUserController helloUser '{clazz,method,isReturn,isThrow,params,target,returnObj,throwExp}'

# 限制观测执行次数
watch *VipUserController helloUser -n 2

# 设置观测结果遍历深度
wathch *VipUserController helloUser -x 2

# 只观测执行成功
watch *VipUserController helloUser -s

# 只观测执行失败
watch *VipUserController helloUser -e

# 同时观测方法执行前、方法执行后结果
watch *VipUserController helloUser -b -f

# 观测方法执行异常时,详细的异常栈信息
watch *VipUserController helloUser '{throwExp}' -e -x 2

# 观测方法执行时间大于200ms的信息
watch *VipUserController helloUser '#cost>100'

常用表达式及含义如下: target : the object clazz : the object's class method : the constructor or method params : the parameters array of method params[0..n] : the element of parameters array returnObj : the returned object of method throwExp : the throw exception of method isReturn : the method ended by return isThrow : the method ended by throwing exception #cost : the execution time in ms of method invocation

用到的命令

watch

案例九. 确认一个方法的调用栈

有一个方法,被多个接口调用,目前已经废弃了一部分,想观测一下目前还有哪些接口在调用这个方法

操作

# 打印出一个方法的调用栈
stack top.learningwang.arthasdemo.service.VipUserService getVipUserByCardNo

用到的命令

stack

案例十.调用回放

排查bug时,联系测试人员复现过一次后,不想每次都麻烦测试人员,想自己多次调用该方法。

操作

# 记录某个方法的执行
tt -t *VipUserController helloUser

# 查看当前记录的方法执行情况
tt -l

# 查看某次调用的详情
tt -i id

# 回放调用
tt -i 1000 -p

用到的命令

案例十一. 确认代码版本

部署完毕后,想确认代码是否为期望版本的代码。

操作

第一种方式: 修改了属性、方法名、参数等,可以通过sc、sm命令进行确认变更。

# 查看类信息及所有的属性
sc -d -f *VipUserController

# 查看类下所有的方法信息
sm -d *VipUserController

# 查看类下具体某个方法的信息
sm -d *VipUserController getVipUser

第二种方式:如果修改了方法内部的实现,可以通过jad反编译得到源码

jad *VipUserController

用到的命令

scjadsm

附1:源码地址

演示命令时用到的springboot web服务源码

附2:学习资料

  1. arthas官方文档
  2. 阿里云arthas在线教程
  3. OGNL表达式官方指南
  4. 强大的OGNL
  5. Java动态追踪技术探究