案例一. 修改日志级别
目前存在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)'
用到的命令
案例二. 更新代码、热加载
线上代码有一行出现了问题,想不重启,修改代码后,实现热加载
操作
# 反编译源码,并保存到文件中
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
用到的命令
案例三. 获取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")'
用到的命令
案例四. 统计方法执行情况
统计一个线上方法一段时间内的调用情况、执行耗时情况、成功次数、异常次数等
monitor命令可以统计一段周期内方法执行的信息:
监控项 | 含义 |
---|---|
timestamp | 时间戳 |
class | Java类 |
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
用到的命令
案例五. 排查死锁问题
线上一个并发执行的方法,长时间未执行完毕,怀疑存在死锁问题。
操作
# 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)
用到的命令
案例六. 排查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
用到的命令
案例七. 排查方法执行慢的问题
线上一个接口访问比较慢,不知道瓶颈再哪个环节。
操作
通过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
用到的命令
案例八. 观察方法调用情况
线上一个接口,日志打印的不全,不加日志,想观测入参、出参、执行结果、异常信息等。
操作
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
用到的命令
案例九. 确认一个方法的调用栈
有一个方法,被多个接口调用,目前已经废弃了一部分,想观测一下目前还有哪些接口在调用这个方法
操作
# 打印出一个方法的调用栈
stack top.learningwang.arthasdemo.service.VipUserService getVipUserByCardNo
用到的命令
案例十.调用回放
排查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
用到的命令
附1:源码地址
演示命令时用到的springboot web服务源码