Arthas-好用的代码热更新&服务监控诊断工具

发布于:2022-11-09 ⋅ 阅读:(685) ⋅ 点赞:(0)

Arthas介绍

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

官网:https://arthas.aliyun.com/

Arthas(阿尔萨斯)能为你做什么?

Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到 JVM 的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从 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.jardemo-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

启动:

image-20221109152258990

启动之后,可以访问80端口:http://localhost/

image-20221109152348412

2、接着我们下载arthas工具

# 下载arthas
wget https://arthas.aliyun.com/arthas-boot.jar
# 启动arthas
java -jar arthas-boot.jar

随后arthas会打印出当前服务器上的java进程供我们选择,此时我们只需要根据不同的进程选择数字后回车即可进入到此进程的交互界面

image-20221109152539945

我这里显示的是3,回车后:此时arthas已经连接上此进程服务,可以交互式执行操作

image-20221109152634051

这里我们简单实用一些命令来看一下arthas的监控情况:输入dashborad可以查看当前系统的实时数据面板,按Q退出界面

image-20221109152847143

输入jvm查看虚拟机信息:

image-20221109153012476

这里是简单介绍两个监控命令,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

image-20221109153528258

可以很明显的看到错误原因位置,那么接下来我要将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文件

image-20221109153855693

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 119 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中了

image-20221109161635093

3.6、浏览器再次验证: http://localhost/user/0

{"id":0,"name":"name0"}

至此代码热更新完成,以上步骤看似较多,其实只要使用熟练就可以快速热更新bug。

小技巧:如果需要修复较复杂的代码,比如需要导入import 类,或者新增函数体等,可以先在IDE中编写代码,随后将.java文件scp到测试环境后使用arthas 热更新即可验证。

本文含有隐藏内容,请 开通VIP 后查看