Arthas介绍
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas(阿尔萨斯)能为你做什么?
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas-代码热更新能力
本文将着重介绍arthas的热更新能力,关于arthas对服务的监控诊断不是本文的重点,想了解如何监控服务可以参考官网
起因
一、在开发 -> 测试环境 -> 灰度环境过程中,我们经常会经历bug提出 -> bug修复 -> 重新部署验证 的过程
二、只是想在代码中添加几条日志打印一下相关信息,却需要重新编译打包重启服务
以上两种无论是对于简单的bug修复还是新增日志代码,都需要重新编译打包并发送到测试环境重启服务,这个过程十分繁琐,常用的更新方式有以下几种:
1、IDE将修复好的代码重新编译打包,再通过scp 、 ftp上传到测试环境后重启服务【最不推荐】
2、将更改后的代码提交到git-hotfix分支上,登录测试环境git -> checkout到hotfix分支,再pull更新代码后,再编译打包重启服务【适合没有使用Jenkins的环境】
3、将更改后的代码提交到git-hotfix分支上,通过Jenkins部署[拉取分支重新编译打包&运行]
以上几种虽然都可以实现bug修复和验证,但都需要重新编译打包并且重启服务
如果我们希望的是无需打包和重启服务,而是直接热更新代码,对于简单的bug实现快速纠错的话,则以上几种都无法实现我们的需求
此时arthas的热更新能力就排上了用场
使用
1、先下载demo-arthas-spring-boot.jar
,demo-arthas-spring-boot
是一个很简单的spring boot应用,源代码:查看
wget https://github.com/hengyunabc/spring-boot-inside/raw/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar
java -jar demo-arthas-spring-boot.jar
启动:
启动之后,可以访问80端口:http://localhost/
2、接着我们下载arthas工具
# 下载arthas
wget https://arthas.aliyun.com/arthas-boot.jar
# 启动arthas
java -jar arthas-boot.jar
随后arthas会打印出当前服务器上的java进程供我们选择,此时我们只需要根据不同的进程选择数字后回车即可进入到此进程的交互界面
我这里显示的是3,回车后:此时arthas已经连接上此进程服务,可以交互式执行操作
这里我们简单实用一些命令来看一下arthas的监控情况:输入dashborad可以查看当前系统的实时数据面板,按Q退出界面
输入jvm查看虚拟机信息:
这里是简单介绍两个监控命令,arthas内部提供了很多强大的监控命令,详细请参考官网介绍。
3、热更新
接下来我们在浏览器输入:http://localhost/user/0,会提示报错:
Something went wrong: 500 Internal Server Error
同时日志会打印错误信息:
java.lang.IllegalArgumentException: id < 1
at com.example.demo.arthas.user.UserController.findUserById(UserController.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.
可以看到报错的代码是在UserController.findUserById函数中,此时我们到arthas的交互界面中输入反编译命令 : jad
[arthas@115725]$ jad com.example.demo.arthas.user.UserController
可以很明显的看到错误原因位置,那么接下来我要将throw new IllegalArgumentException(“id < 1”); 这行代码注释掉并且更新,该如何操作呢?
3.1、先将UserController反编译到一个目录下
# --source-only意思是只将代码打印
[arthas@115725]$ jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
3.2、vi 修改 /tmp/UserController.java文件
3.3、通过sc命令查找加载UserController的ClassLoader
由于类的加载过程都是通过其加载器完成,故需要先找到UserController的ClassLoader,后面会用到,可以看到UserController是通过LaunchedURLClassLoader加载的
[arthas@115725]$ sc -d *UserController | grep class-loader
class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@5b2133b1
3.4、将改好的/tmp/UserController.java 使用mc(Memory Compiler)命令来编译,并且通过–classLoaderClass参数指定ClassLoader:
[arthas@115725]$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 905 ms.
此时已经将改好的UserController.java编译到了: /tmp/com/example/demo/arthas/user/UserController.class
$ ll /tmp/com/example/demo/arthas/user/
总用量 4
-rw-rw-r-- 1 bdp bdp 1359 11月 9 16:12 UserController.class
3.5、使用redefine命令重新加载新编译好的UserController.class
[arthas@115725]$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1, classes:
com.example.demo.arthas.user.UserController
显示加载成功,我们再反编译jad看一下UserController.class,可以看到已经加载进JVM中了
3.6、浏览器再次验证: http://localhost/user/0
{"id":0,"name":"name0"}
至此代码热更新完成,以上步骤看似较多,其实只要使用熟练就可以快速热更新bug。
小技巧:如果需要修复较复杂的代码,比如需要导入import 类,或者新增函数体等,可以先在IDE中编写代码,随后将.java文件scp到测试环境后使用arthas 热更新即可验证。