1. 背景
在过去的几年以及未来,做为云服务的单位或个人都会遇到一些问题:
l 当你线上项目出了问题,但是一打开日志发现,有些地方忘记打了日志,于是你马上补上日志,然后重新上线。这个在一些上线流程不规范的公司还比较轻松,在一些流程比较严格,比如美团上线的时候就有封禁期,一般就只能9点之后才能上线。有可能这样一拖就耽误了解决问题的黄金时刻。
l 当你的项目某个接口执行速度较慢,为了排查问题,于是你四处加上每个方法运行时间。
l 当你发现某个类有冲突,好像在线上运行的结果和你预期的不符合,手动把线上编译出的class文件下载下来然后反编译,看看究竟class内容是什么。
l 当代码已经写好准备联调,但是下游业务环境并没有准备好,于是你把以前的代码依次进行注释,采用mock的形式又写了一遍方便联调。
1.1. 常见处理方式
为了解决这些问题,许多公司耗费了大量的精力来补日志、排查jar、还有许多公司自研性能监控工具(通过分层监控实现)甚至由运维人员通过jConsole等工具实时监控JVM运行状况。以往我们在实时监控和排查问题时都采用的时jdk中的一些辅助工具,如:jps、jstack、jhat、jmap、jstat和可视化工具jConsole、jvisualvm等来排查和监控线上JAVA应用的问题。
今天为大家介绍一款由阿里开源的一款Java在线问题诊断工具。
2. Arthas介绍
2.1. 是什么
Arthas是一款能够实时查看系统运行状态、监控JVM的运行状态、查看类的依赖关系、获取堆栈信息。它能帮主研发人员解决以下痛点:
l 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
l 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
l 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
l 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
l 是否有一个全局视角来查看系统的运行状况?
l 有什么办法可以监控到JVM的实时运行状态?
2.2. 常用问题
2.2.1. 类加载错误
相信大家在研发或查看生产日志时或多或少都由遇到或NoSuchMethodError这个错误,经验丰富的研发一眼就知道时jar版本冲突造成的,轻松就解决掉此类问题。
类似这种问题,如何通过Arthas能够快速定位呢?
在使用Arthas命令行定位问题之前,首先启动其,通过jps命令找到jvm中你要监控的应用线程,并启动arthas,如图:
上图中由于本机未开启telnet,故可以通过网址访问,来操作Arthas命令行。
2.2.1.1. sc命令
找到对应的类和出错的方法,然后输出下面的命令
可以看见打印出了code-source,当时发现了code-source并不是从对应的Jar包取出来的,于是发现了两个服务对于同一个类使用了同样的包名和类名,导致了这个奇怪的问题,后续通过修改包名和类名进行解决。
sc原理:sc的信息主要从对应的Class中获取。比如isInterface,isAnnotation等等都是通过下面的方式获取:
对于我们上面的某个类从哪个jar包加载的是通过CodeSource来进行获取的:
2.2.1.2. jad命令
Arthas还提供了一个命令jad用来反编译,,对于解决类冲突错误很有用,比如我们想知道这个类里面的代码到底是什么,直接一个jad命令就能搞定。一般通过这个命令我们就能发现和你所期待的类是否缺少了某些方法,或者某些方法有些改变,从而确定jar包冲突。
2.2.2. 如何知道某个方法是否被调用
2.2.2.
2.2.2.1. watch
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
参数说明:
watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象。
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。(ps:ognl表达式的用法这里不做说明,感兴趣的同学,请参考以下链接学习。)
特殊用法请参考:https://github.com/alibaba/arthas/issues/71;
OGNL表达式官网:https://commons.apache.org/proper/commons-ognl/language-guide.html
特殊说明:
l watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
l 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
l 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
l 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在
参考示例:
命令参数详解:
# 观察CommonTest的test方法 # 输出 入参、返回结果、抛出的异常 —— 输出的内容可以动态调整 # 后面跟着的是 条件表达式,表示耗时超过10ms才输出 # -n 表示只执行一次,-x表示 入参和返回结果的展开层次为5层
watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10' -x 5 -n 1
# 耗时大于10ms并且第一个参数等于1才输出
watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10 && params[0]==1' -x 5 -n 1
# 第一个参数大于1 并且第二个参数等于hello才输出
watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]>1 && params[1]=="hello"' -x 5 -n 1
# 第一个参数小于5或者第二个参数等于"world"就输出
watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]<5 || params[1]=="wolrd"' -x 5 -n 1
# 第一个参数的name字段等于world时才输出。
# 由于在方法执行过程中参数的name属性可能发生改变,因此加上-b才能观察到真正的入参
watch -b *.CommonTest test "{params,returnObj,throwExp}" 'params[0].name=="wolrd"' -x 5 -n 1
# 由于同时指定了-s和-b,所以方法被调用一次,就会输出2次结果(两个场景分开输出),分别是方法被调用前,和返回之后
# 注意,这里如果-n只设置成1,那么只会输出-b对应的输出,-s对应的输出由于没有次数了就无法输出了
watch *.CommonTest test '{params,returnObj,throwExp}' -x 5 -n 2 -s -b
2.2.3. 如何知道某个方法耗时多
2.2.3.
2.2.3.1. trace
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。但是trace只能追踪一层的调用链路,如果一层的链路信息不够用,可以把该链路上有问题的方法再次进行trace。
上图中可以看到使用了两次trace。第一次时,发现service层的方法比较耗时,故在第二次trace时特意针对该层方法追踪,就可以看到service层方法中调用的那个方法耗时较长。
2.2.4. 如何使用命令重发请求
有时候排查一个问题需要上游再次调用这个方法,比如使用postMan等工具,当然Arthas提供了一个命令让替代我们来回手动请求。
2.2.4.
2.2.4.1. tt
tt官方介绍: 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测。可以看见tt可以用于录制请求,当然也支持我们重放。
录制某个方法的命令如下:
上面录制了3个调用环境现场,也可以看做是录制了3个请求返回信息。比如我们想选择index为1001个的请求来重放,可以输入下面的命令。
本文暂时没有评论,来添加一个吧(●'◡'●)