目录
在线文档:
https://alibaba.github.io/arthas
一、前言
1、热更新代码的场景
(1)当线上服务器出现问题时,有些时候现有的手段不足以发现问题所在,可能需要追加打印日志或者增加一些调试代码,如果我们去改代码重新部署,会破坏问题现场,可以通过热部署的手段来增加调试代码
(2)线上出现紧急bug,通过Review代码找到问题,修改好后打包部署的流程可能比较久,可以通过热部署代码及时解决问题
二、Arthas的使用
使用阿里巴巴开源的Java诊断工具---Arthas,他可以附着在我们的Java服务器进程上面,查看服务器状态,jvm状态等各种参数指标,还可以进行热更新
1、下载启动Arthas
-
wget https://alibaba.github.io/arthas/arthas-boot.jar
-
java -jar arthas-boot.jar
2、启动后会显示当前机器上面所有的java进程,选择我们需要监控/修改的进程,输入序号回车
3、一些常用命令,如果线上出现问题,可以通过以下命令查看各项指标是否有异常
-
dashboard——当前系统的实时数据面板
-
thread——查看当前 JVM 的线程堆栈信息
-
jvm——查看当前 JVM 的信息
-
sysprop——查看和修改JVM的系统属性
-
sysenv——查看JVM的环境变量
-
getstatic——查看类的静态属性
(1)打印前五名最消耗CPU的线程,可以及时找到CPU过高的代码位置
thread -n 5
(2)查看某个函数的调用堆栈
stack <类全包名> <函数名>
(3)查看某个函数的哪个子调用最慢,耗时最久的调用会标红显示,可以方便找出某个功能中最耗时的操作
trace <类全包名> <函数名>
(4)监控某个函数的调用统计数据,包括总调用次数,平均运行时间,成功率等信息
monitor <类全包名> <函数名>
4、输入exit可以退出当前的连接,但是附着在服务器进程上的Arthas依然在运行,完全退出可以输入shutdown
三、热更新
1、首先找到我们需要更新代码的全包名,通过jad命令将线上正在运行的代码反编译出来
jad --source-only <全包名> > <导出目录+文件名>
2、拿到java代码后,我们根据需求来修改代码,需要注意的是这里热更新代码的实际原理是调用Java基础类java.lang.instrument.Instrumentation的redefineClasses方法,他可以通过修改字节码来替换已有的class文件,其中有诸多的限制:
(1)比如不能增加或删除field/method
(2)没有退出的函数不能生效,比如一个函数体内是一个where(true)循环,永远不会结束,那么我们修改的代码也永远不会生效
我们可以在函数中增加一些代码,比如增加日志打印等
3、修改好代码后,我们要找到这个这个类对应的类加载器,再去加载这个class,执行如下命令会返回类加载器的对象地址
sc -d <全包名> | grep classLoaderHash
4、通过内存编译将Java文件编译成Class文件
mc -c <类加载器的对象地址> <Java文件所在目录+文件名>
5、最后,我们通过命令将class文件进行热更新
redefine <Class文件所在目录+文件名>
6、更新完毕不出意外会立即生效,这时候就可以去验证代码是否生效了
-
Arthas进阶教程
-
在例子里,访问 curl http://localhost/user/0
,会返回500错误:
{
"timestamp": 1550223186170,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.IllegalArgumentException",
"message": "id < 1",
"path": "/user/0"
}
下面通过热更新代码,修改这个逻辑。
jad反编译代码
反编译UserController
,保存到 /tmp/UserController.java
文件里。
jad --source-only com.example.demo.arthas.user.UserController > tmp/UserController.java
修改反编绎出来的代码
用文本编辑器修改/tmp/UserController.java
,把抛出异常改为正常返回:
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" id);
}
sc查找加载UserController的ClassLoader
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
可以发现是spring boot的 LaunchedURLClassLoader@1be6f5c3
加载的。
mc内存编绎代码
保存好/tmp/UserController.java
之后,使用mc(Memory Compiler)命令来编译,并且通过-c
参数指定ClassLoader
:
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
redefine热更新代码
再使用redefine命令重新加载新编译好的UserController.class
:
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
检验热更新结果
再次访问 curl http://localhost/user/0
,会正常返回:
{
"id": 0,
"name": "name0"
}
总结
Arthas里 jad
/mc
/redefine
一条龙来线上热更新代码,非常强大,但也很危险,需要做好权限管理。
比如,线上应用启动帐号是 admin,当用户可以切换到admin,那么
-
用户可以修改,获取到应用的任意内存值(不管是否java应用)
-
用户可以attach jvm
-
attach jvm之后,利用jvm本身的api可以redefine class
所以:
-
应用的安全主要靠用户权限本身的管理
-
Arthas主要是让jvm redefine更容易了。用户也可以利用其它工具达到同样的效果
最后,Arthas提醒您: 诊断千万条,规范第一条,热更不规范,同事两行泪。