利用 Arthas 实现线上热更新:快速修复问题,无需重启服务

引言

在软件开发的快速迭代中,能够在不导致生产环境停机的情况下快速解决问题至关重要。

本文将指导你如何使用 Arthas 工具进行热更新,这是一种强大的 Java 诊断工具。我们将涵盖背景、操作步骤以及需要注意的重要事项。

业务背景

1. 系统功能迭代频繁与复杂场景

现代软件系统通常需要快速迭代以跟上不断变化的业务需求。这些频繁的更新有时会在复杂的业务场景中导致意外问题。例如,支付处理模块的一个小更改可能会意外影响一个未在初始部署期间充分测试的特定用例。

2. 客户对停机的抵触

当今竞争激烈的环境中,客户要求高可用性和最小的中断。在某些情况下,服务器性能不佳,重启服务所需的时间被视为一个重大障碍。这种对停机的抵触使得传统更新方法变得不切实际,特别是对于需要立即处理的紧急修复。

因此,相较于整体更新发布,热更新可以很好的解决上述问题,热更新通过允许开发人员在不重启运行中的应用程序的情况下修改其行为,提供了一种解决方案。这对于关键的错误修复或无法等待计划维护窗口的小功能调整特别有用。

操作步骤

Arthas 是阿里巴巴开发的一款开源 Java 诊断工具。它提供了一套强大的命令,可用于检查和修改 Java 应用程序的运行时行为。

redefine 命令是实现热更新的关键功能之一。

步骤 1:编译并上传类文件

  1. 本地开发环境编译
    • 在本地开发环境中,使用首选的构建工具(例如 Maven、Gradle)编译修改后的 Java 类文件。
    • 确保编译后的 .class 文件与生产服务器上运行的 Java 版本兼容。
  2. 上传到文件服务器
    • 将编译后的 .class 文件传输到生产环境可以访问的文件服务器。可以使用 scpftp 或基于云的存储解决方案来完成此操作。

步骤 2:在目标服务器上获取类文件

在生产服务器上,使用 curl 命令从文件服务器下载更新后的类文件:

curl https://file.chenlijian.cn/Demo.class -o /tmp/Demo.class

步骤 3:使用 Arthas 重新定义类

  1. 启动 Arthas Agent

    • 使用以下命令将 Arthas Agent 附加到目标 Java 进程:
    curl -O https://arthas.aliyun.com/arthas-boot.jar
    java -jar arthas-boot.jar
    
    • 从可用的 Java 进程列表中选择目标进程。
  2. 使用 redefine 命令

    • 使用 redefine 命令加载更新后的类文件:
    redefine /tmp/Demo.class
    
    • 该命令将用新的类定义替换内存中的现有类定义,使更新后的代码立即生效。
  3. 完整命令行实例

# curl https://image.yujian95.cn/blog/IccsPlanUtil.class -o /tmp/IccsPlanUtil.class
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  9876  100  9876    0     0  28136      0 --:--:-- --:--:-- --:--:-- 28217

# curl https://image.yujian95.cn/blog/BatchScheduleOnlyDispatchPlanProcessor.class -o /tmp/BatchScheduleOnlyDispatchPlanProcessor.class
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 22564  100 22564    0     0  61588      0 --:--:-- --:--:-- --:--:-- 61482

# curl https://image.yujian95.cn/blog/SchedulePlanServiceImpl.class -o /tmp/SchedulePlanServiceImpl.class
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 76963  100 76963    0     0   171k      0 --:--:-- --:--:-- --:--:--  171k

# curl https://image.yujian95.cn/blog/SchedulePlanRelDataServiceImpl.class -o tmp/SchedulePlanRelDataServiceImpl.class
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10714  100 10714    0     0  34105      0 --:--:-- --:--:-- --:--:-- 34121

# java -jar arthas-boot.jar
[INFO] JAVA_HOME: /usr/local/openjdk-8/jre
[INFO] arthas-boot version: 4.0.4
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 6 app.jar
1
[INFO] arthas home: /root/.arthas/lib/4.0.4/arthas
[INFO] The target process already listen port 3658, skip attach.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.                           
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'                          
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.                          
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |                         
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'                          

wiki       https://arthas.aliyun.com/doc                                        
tutorials  https://arthas.aliyun.com/doc/arthas-tutorials.html                  
version    4.0.4                                                                
main_class                                                                      
pid        6                                                                    
time       2025-02-08 13:51:12.219                                              

[arthas@6]$ redefine /tmp/IccsPlanUtil.class
redefine success, size: 1, classes:
com.t.k.iaps.util.IccsPlanUtil

[arthas@6]$ redefine /tmp/BatchScheduleOnlyDispatchPlanProcessor.class
redefine success, size: 1, classes:
com.t.k.iaps.processor.impl.schedule.BatchScheduleOnlyDispatchPlanProcessor

[arthas@6]$ redefine /tmp/SchedulePlanRelDataServiceImpl.class
redefine success, size: 1, classes:
com.t.k.iaps.service.schedule.impl.SchedulePlanRelDataServiceImpl

[arthas@6]$ redefine /tmp/SchedulePlanServiceImpl.class
redefine success, size: 1, classes:
com.t.k.iaps.service.schedule.impl.SchedulePlanServiceImpl

重要注意事项

redefine 命令的限制

  1. 不能修改字段和方法
    • redefine 命令不支持修改、添加或删除类的字段和方法。这包括方法参数、方法名称和返回类型的更改。
    • 例如,如果你尝试向类中添加一个新方法,然后使用 redefine,运行时将不会识别该新方法。
  2. 与其他 Arthas 命令的冲突
    • redefine 命令可能与其他 Arthas 命令(如 jadwatchtracemonitortt)冲突。
    • 如果在执行 redefine 后执行上述任何命令,更新后的字节码可能会被重置,导致更改失效。

参考资料