V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
8629
V2EX  ›  Java

请教下 Java 热替换代码的技术

  •  1
     
  •   8629 · 2022-09-23 10:55:09 +08:00 · 5789 次点击
    这是一个创建于 552 天前的主题,其中的信息可能已经有所发展或是发生改变。
    场景:把接收处理数据的类 源码放在 web 后台页面中在线编辑,编辑完这个类直接提交到 redis 中。然后线上服务就替换了这部分代码,不需要打包发布实现了更新服务。
    请问这是什么技术,什么关键字可以查询,有没有现成的 demo 可以学习下。感谢!
    59 条回复    2022-09-30 15:25:52 +08:00
    nothingistrue
        1
    nothingistrue  
       2022-09-23 10:57:40 +08:00   ❤️ 1
    你这要能实现,得推翻 Java 的编译原理。
    sunjiayao
        2
    sunjiayao  
       2022-09-23 10:59:11 +08:00   ❤️ 1
    groovy 你值得拥有
    XiLingHost
        3
    XiLingHost  
       2022-09-23 10:59:25 +08:00
    把新的代码自动构建成一个镜像,然后调整服务路由做灰度发布就行了,调用方可以无感
    wxw752
        4
    wxw752  
       2022-09-23 11:00:49 +08:00   ❤️ 3
    就因为这个问题,我们公司到现在还有一块业务用的 PHP
    LeegoYih
        5
    LeegoYih  
       2022-09-23 11:01:14 +08:00
    GitHub 搜 Java HotSwap 还是有挺多 demo 的,不过能替换的程度有限
    twinsdestiny
        6
    twinsdestiny  
       2022-09-23 11:02:21 +08:00
    groovy 可以
    justicelove
        7
    justicelove  
       2022-09-23 11:02:24 +08:00
    groovy
    wangxiaoaer
        8
    wangxiaoaer  
       2022-09-23 11:03:07 +08:00   ❤️ 2
    @nothingistrue ??? 怎么叫推翻呢。

    javax.tools 本身就提供了编译 java 文件的途径,再加上 classloader 替换,技术上应该是可行的。
    superchijinpeng
        9
    superchijinpeng  
       2022-09-23 11:04:03 +08:00
    superchijinpeng
        10
    superchijinpeng  
       2022-09-23 11:04:50 +08:00
    @nothingistrue 无非是 Class Load 和 Unload 一下
    chendy
        11
    chendy  
       2022-09-23 11:04:56 +08:00
    编译 api+反射,不是替换是新增……
    每次生成一个新类名,编译出 class 文件,再把调用的地方的类名替换上去,完事
    justicelove
        12
    justicelove  
       2022-09-23 11:06:22 +08:00
    可以搜一下 java 脚本 一般都会选择 Groovy, 也可以使用 spi, 在服务器上替换 jar 包
    aguesuka
        13
    aguesuka  
       2022-09-23 11:07:02 +08:00
    鉴于有在线编辑的需求, 建议直接用 groovy 脚本. 完全兼容 java 对象, 无需编译, 也就不需要热替换.
    Dxxxxs
        14
    Dxxxxs  
       2022-09-23 11:09:01 +08:00
    可以看一下 jvm 提供的 Instrumentation 类。JRebel 、springboot dev tools 、HotSwapAgent 都提供了类似的实现
    pannanxu
        15
    pannanxu  
       2022-09-23 11:13:40 +08:00
    PF4J
    Jooooooooo
        16
    Jooooooooo  
       2022-09-23 11:20:12 +08:00
    自定义 classloader 就行. 加载这个 jar. 然后跑方法的时候用反射拿到你热加载的那个.

    我们刚好有这么搞的, 引入算法包天天变, 就用的这个方法不用发版能用上最新的

    不过有几个注意点你得考虑下:

    1. 有 load 记得要 unload
    2. 如果是集群, 要保证所有集群都加载完了再去用, 所以需要维护机器当前加载某个 jar 的状态, 最好有个后台去查看和使用, 全部机器都加载完了才能真正使用
    VYSE
        17
    VYSE  
       2022-09-23 11:37:35 +08:00
    关键字: JAVA 反序列化漏洞
    这么搞后台得管控好
    m2276699
        18
    m2276699  
       2022-09-23 11:43:47 +08:00
    cn.hutool.core.compiler/sofa ark
    cpstar
        19
    cpstar  
       2022-09-23 11:46:32 +08:00
    java 语言层面支持。然后就是你的应用底层需要支持。我目前正在用的一个平台微应用化就支持动态加载,关闭某个应用或者打开,更新了 jar 之后动态重加载。
    但是我目前用的平台支持 beanshell 、groovy 、javascript 的在线编辑和编译,java 代码的编辑和编译再加载不知道有没有库。javascript 方面使用的是 apache rhino 库,翻译 js 并运行。
    ic2y
        20
    ic2y  
       2022-09-23 11:50:56 +08:00   ❤️ 1
    为什么不用规则引擎,专门处理这种事。例如 Aviator
    xiangxiangxiang
        21
    xiangxiangxiang  
       2022-09-23 11:51:30 +08:00
    groovy 脚本+1 之前有场景就是 m 端维护 /发布代码块,然后在 c 端动态加载生效
    Vegetable
        22
    Vegetable  
       2022-09-23 11:53:56 +08:00
    标准的 RPC API 、自动构建、容器化
    virusdefender
        23
    virusdefender  
       2022-09-23 12:01:08 +08:00
    yuanliubei
        24
    yuanliubei  
       2022-09-23 12:25:35 +08:00
    coala
        25
    coala  
       2022-09-23 12:28:56 +08:00
    类似 JSP 呗.
    q1angch0u
        26
    q1angch0u  
       2022-09-23 12:31:31 +08:00 via iPhone
    grpovy 啊…
    paullee
        27
    paullee  
       2022-09-23 12:35:04 +08:00 via iPhone
    花这些功夫,用 k8s 部署,滚动更新,不是更舒服?
    ggbond2
        28
    ggbond2  
       2022-09-23 12:35:09 +08:00
    humpy
        29
    humpy  
       2022-09-23 12:54:53 +08:00
    可以做,jdk 提供了 JavaCompiler ,可以在运行时编译代码,将编译后的代码存在内存里,再实现一个 ClassLoader ,就能加载刚编译的类了。

    可以参考一下这篇文章,好像是微博的老师写的:
    https://zhenbianshu.github.io/2019/12/play_with_java_dynamic_compile.html
    humpy
        30
    humpy  
       2022-09-23 12:56:03 +08:00
    misaka19000
        31
    misaka19000  
       2022-09-23 13:10:52 +08:00 via Android
    可以用 ASM 动态替换字节码来做,或者用 ByteBuddy 使用更高级一些的 API
    molika
        32
    molika  
       2022-09-23 13:35:39 +08:00
    jvm 上用 Clojure 天生支持
    codehz
        33
    codehz  
       2022-09-23 13:38:36 +08:00
    还记得之前的 jndi 漏洞吗(
    听着就是在造 RCE(
    xuanbg
        34
    xuanbg  
       2022-09-23 13:45:54 +08:00
    能搞!办法还不少,但没一个是安全的。想想也知道,这就相当于代码不经过审查和测试就直接上线,我写几个漏洞也是没什么问题的吧?谁还不写几个 bug 呢。。。
    nothingistrue
        35
    nothingistrue  
       2022-09-23 13:54:19 +08:00
    @wangxiaoaer #8
    @superchijinpeng #10
    仔细看:“编辑完这个类直接提交到 redis 中”,“不需要打包”。这是想源码一步到底的,Classloader 可 load 不了。
    superchijinpeng
        36
    superchijinpeng  
       2022-09-23 14:18:05 +08:00
    @nothingistrue URLClassLoader ,参考 Spark 或者 Flink 动态注册或移除 UDF
    zhang77555
        37
    zhang77555  
       2022-09-23 14:24:58 +08:00
    JavaCompiler 把代码编译成 class 然后 URLClassLoader 加载
    建议定好接口和编码模板校验,免得这部分功能被滥用
    selca
        38
    selca  
       2022-09-23 14:27:00 +08:00
    @codehz #33 私有的东西,目的就是造一个门去调,如果操作者能控制好鉴权,还是没问题的
    dddyyyttt
        39
    dddyyyttt  
       2022-09-23 14:55:04 +08:00
    为什么没人提 arthas ?
    wangxiaoaer
        40
    wangxiaoaer  
       2022-09-23 14:55:43 +08:00
    @nothingistrue 我理解他的意思是 java 代码存到 redis ,但是肯定不能和直接用,后台可以从 redis 读这些代码编译,替换。

    如果想直接从 redis 加载 java 文件就替换运行,那肯定是不行的。
    3032
        41
    3032  
       2022-09-23 14:59:05 +08:00
    阿里的阿尔萨斯了解下
    vvtf
        42
    vvtf  
       2022-09-23 15:07:16 +08:00
    1. 通过 Agent 拿到 Instrumentation
    2. 通过 Instrumentation#redefineClasses 替换类即可.
    leegradyllljjjj
        43
    leegradyllljjjj  
       2022-09-23 15:15:55 +08:00
    v 我 50 ,我帮你守着服务器,你一提交代码我就帮你编译发布
    zgzhang
        44
    zgzhang  
       2022-09-23 15:23:09 +08:00
    这样的东西很成熟呀,我做的类似的项目,核心原理就是 Java 的动态编译+spring bean 的替换,如果有需要可以联系我
    BiChengfei
        45
    BiChengfei  
       2022-09-23 15:58:53 +08:00
    magic-api
    warcraft1236
        46
    warcraft1236  
       2022-09-23 16:43:22 +08:00
    这个跟热更新不是一个原理吗
    thisisgpy
        47
    thisisgpy  
       2022-09-23 17:45:53 +08:00
    先把 class restransform 回来,记录一下当前在用 classloader 的 hashcode ,新的代码编译后找到刚才的 classloader rebase 进去
    Znemo
        48
    Znemo  
       2022-09-23 18:11:57 +08:00
    classloader 就能做到,但是这要围绕这种编程模型来架构,一般的业务代码可以这样热替换,核心代码例如 class 的加载、事件调度等就很难做到了,另外方法区的垃圾回收要关注,被替换掉的 class 要有有效的回收机制。除非精心设计,否则需要注意的问题还是蛮多的。
    hetal
        49
    hetal  
       2022-09-23 19:02:22 +08:00   ❤️ 1
    换成 php 是不是更简单~
    viakiba
        50
    viakiba  
       2022-09-23 20:03:10 +08:00
    写过这个介绍, 可以参考 https://blog.viakiba.cn/2020/03/30/java-hot-fix/
    byte10
        51
    byte10  
       2022-09-23 20:20:01 +08:00
    OSGI 框架 应该也可以满足 OP 需求,另外还有一种 hotswap , hook 技术都可以做到,并不是特别复杂的事情,可以多了解下。
    iloveios
        52
    iloveios  
       2022-09-23 21:15:23 +08:00 via iPhone
    赞同 49 楼
    muhuan
        53
    muhuan  
       2022-09-23 21:51:22 +08:00
    songco
        54
    songco  
       2022-09-24 02:57:49 +08:00 via iPhone
    Groovy 加 1

    以前做过一个比较大的平台,大量使用 groovy ,还是比较好用的,缺点是复杂逻辑用 groovy 容易埋坑

    Classloader 我在项目中也大量使用过,类似实现了一种插件机制,插件的升级就相当于替换了
    westoy
        55
    westoy  
       2022-09-24 03:56:07 +08:00 via Android
    @hetal

    其实 erlang 那种才是

    php 其实并不是热更新,并发大一点很容易在更新时触发一半新版本夹了几个旧版本文件

    上古严谨点的 php 项目也是通过部署最新版本到一个新目录,然后启动新服务替代掉老服务,包括 zend 搞的 phpcloud ,推送文件变更后立刻访问也会提示正在重启应用
    ychost
        56
    ychost  
       2022-09-26 22:29:30 +08:00
    建议 Groovy ,别搞 Java 热加载了一堆坑,
    LiangLin
        57
    LiangLin  
       2022-09-28 19:54:39 +08:00
    jsp 了解以下
    b2byco
        58
    b2byco  
       2022-09-29 10:44:52 +08:00
    Janino
    http://janino-compiler.github.io/janino/

    The ShippingCost class demonstrates how easy it is to use Janino as an expression evaluator.
    The ExpressionDemo class implements a command line-based test environment for the expression evaluator.
    The ScriptDemo class implements a command line-based test environment for the script evaluator.
    The ClassBodyDemo class implements a command line-based test environment for the class body evaluator.
    ggbond2233
        59
    ggbond2233  
       2022-09-30 15:25:52 +08:00
    QLExpress
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   987 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 20:16 · PVG 04:16 · LAX 13:16 · JFK 16:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.