Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arthas实践--jad/mc/redefine线上热更新一条龙 #537

Closed
hengyunabc opened this issue Feb 20, 2019 · 11 comments
Closed

Arthas实践--jad/mc/redefine线上热更新一条龙 #537

hengyunabc opened this issue Feb 20, 2019 · 11 comments

Comments

@hengyunabc
Copy link
Collaborator

背景

尽管在生产环境热更新代码,并不是很好的行为,很可能导致:热更不规范,同事两行泪。

但很多时候我们的确希望能热更新代码,比如:

线上排查问题,找到修复思路了,但应用重启之后,环境现场就变了,难以复现。怎么验证修复方案?

又比如:

本地开发时,发现某个开源组件有bug,希望修改验证。如果是自己编译开源组件再发布,流程非常的长,还不一定能编译成功。有没有办法快速测试?

Arthas是阿里巴巴开源的Java应用诊断利器,深受开发者喜爱。

下面介绍利用Arthas 3.1.0版本的 jad/mc/redefine 一条龙来热更新代码。

Arthas在线教程

下面通过Arthas在线教程演示热更新代码的过程。

arthas-online-hotswap

在例子里,访问 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提醒您: 诊断千万条,规范第一条,热更不规范,同事两行泪

Arthas实践系列

@hengyunabc
Copy link
Collaborator Author

如果有一些类比较复杂,那么有可能jad命令反编绎失败。那么后面的mc命令也会失败。

这时,可以在本地修改代码,然后把.class文件上传到服务器上。

有一些服务器的权限比较严格,不允许直接上传文件,那么可以用一些技巧来绕过。比如传用base64命令:

  1. 在本地先转换.class文件为base64,再保存为result.txt
base64 < Test.class > result.txt
  1. 到服务器上,新建result.txt,复制本地的内容,粘贴再保存

  2. 把服务器上的 result.txt还原为.class

base64 -d < result.txt > Test.class
  1. 用md5命令计算哈希值,校验是否一致

@CjqDy
Copy link

CjqDy commented Feb 27, 2019

mc 内存编译的时候会根据类所在的包路径在服务器生成指定路径,是这样吧?
redefine也是根据这个路径同步更新包里的class文件的

@hengyunabc
Copy link
Collaborator Author

mc 内存编译的时候会根据类所在的包路径在服务器生成指定路径,是这样吧?
redefine也是根据这个路径同步更新包里的class文件的

mc 不指定输出目录,默认就是target进程的工作目录。可以用pwd命令来查看。

@VanXD
Copy link

VanXD commented Mar 1, 2019

如果有一些类比较复杂,那么有可能jad命令反编绎失败。那么后面的mc命令也会失败。

这时,可以在本地修改代码,然后把.class文件上传到服务器上。

有一些服务器的权限比较严格,不允许直接上传文件,那么可以用一些技巧来绕过。比如传用base64命令:

  1. 在本地先转换.class文件为base64,再保存为result.txt
base64 < Test.class > result.txt
  1. 到服务器上,新建result.txt,复制本地的内容,粘贴再保存
  2. 把服务器上的 result.txt还原为.class
base64 -d < result.txt > Test.class
  1. 用md5命令计算哈希值,校验是否一致

本地修改较为复杂的情况, 检查class是否能热更新可在idea中按ctrl+shift+f9

@xiexingguang
Copy link

nice

@wanglei134
Copy link

编译一个类 但是这个类import了其他jar包的类 会报错该怎么办呢

image

@hengyunabc
Copy link
Collaborator Author

@wangdonghello 看wiki,本地编译上传

@gaochengyidlmu
Copy link

@hengyunabc
现在遇到泛型擦除的问题,通过 jad 反编译得到的java文件中,原本是 List para1 的变量,变成了 ArrayList para1,之后 mc 时,就会报错
Memory compiler error, exception message: Compilation Error
message: incompatible types: java.lang.Object cannot be converted to XXX

这个有没有什么办法可以处理,我现在想通过 arthas 的这个功能,进行批量操作,所以本地编译class行不通。

@Contentsearch
Copy link

image
执行jad 反编译文件返回的是null 什么情况

@cwdhf
Copy link

cwdhf commented Oct 11, 2023

image 执行jad 反编译文件返回的是null 什么情况

写入指定文件不要用./ 如果指定目录直接使用绝对地址,如果不指定就不要填

@MaidSG
Copy link

MaidSG commented Aug 5, 2024

image 按流程操作未碰到问题,但是最后验证时并未变更是什么原因,用到了lombok的@builder注解,编译时生成了两个文件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants