V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
limpo
V2EX  ›  Go 编程语言

新玩具来了 go-hotfix,用于运行时热修复函数

  •  
  •   limpo ·
    limpo1989 · 17 天前 · 2397 次点击

    基于 monkey-patch + plugin 机制实现的热修复方案,用于实现不停服修复 bug!!

    仓库地址: https://github.com/go-hotfix/hotfix

    感兴趣可以玩一下 example/webapp 这个例子,注意仅支持 Linux ( ps: Windows 可以在 wsl2 里面玩)

    hotfix

    hotfix is a golang function hot-fix solution


    警告: 目前尚未经过严格测试,请勿用于生产环境
    Warning: This has not been rigorously tested, do not use in production environment

    注意: 不支持 Windows
    Note: Windows is not supported

    注意: 为了确保函数都能被修补,需要关闭函数内联,会因此损失一些性能
    Note: To ensure that all functions can be patched, function inlining needs to be disabled, which will result in a loss of some performance

    Features

    • 支持指定包级别/类级别/函数级别热补丁支持
    • Supported for hot patching at package/class/function
    • 支持导出函数/私有函数/成员方法修补
    • Support for exporting functions/private functions/member methods patching
    • 基于monkey-patch + plugin机制实现
    • Implemented based on monkey-patch + plugin
    • 线程安全, 使用 stw 确保所有协程都进入安全点从而实现线程安全的补丁
    • Thread safety, use stw to ensure that all coroutines enter safe points to hot patching

    Limits

    • 受限于go plugin仅支持 Linux, FreeBSD, macOS ,其他平台目前不支持
    • The go plugin is currently only supported on Linux, FreeBSD, and macOS platforms; other platforms are not supported
    • 加载的补丁包无法卸载,如果热修复次数过多可能导致较大的内存占用
    • The loaded patch package cannot be uninstalled. Too many hot fixes may result in large memory usage
    • 不支持对闭包进行修复,需要热修复的逻辑不要放在闭包中
    • Closures are not supported, and logic that requires hotfixes should not be placed in closures
    • 不能修改已有数据结构和函数签名,否则可能将导致程序崩溃,应该仅用于 bug 修复
    • Cannot modify existing data structures and function signatures, as this may lead to program crashes. It should only be used for bug fixes
    • 编译时请保留调试符号,并且禁用函数内联-gcflags=all=-l
    • Please keep the debugging symbols when compiling, and disable function inline -gcflags=all=-l
    • 编译错误invalid reference to xxxx 是因为 go1.23开始限制了go:linkname功能,必须添加编译参数关闭限制-ldflags=-checklinkname=0
    • The compilation error invalid reference to xxxx is because go1.23 began to limit the go:linkname function, and the compilation parameter must be added to turn off the restriction -ldflags=-checklinkname=0
    • 补丁包的的编译环境必须和主程序一致,包括 go 编译器版本,编译参数,依赖等,否则加载补丁包将会失败
    • The patch package's build environment must match that of the main program, including the Go compiler version, compilation parameters, dependencies, etc., otherwise loading the patch package will fail
    • 补丁包的main包下面的init会首先调用一次,请注意不要重复初始化
    • The 'init' under the 'main' package of the patch package will be called once first, be careful not to initialize it repeatedly
    • 打补丁包时main包必须产生变化,否则可能出现 plugin already loaded错误,推荐使用 -ldflags="-X main.HotfixVersion=v1.0.1"指定版本号, 确保每次编译补丁包都会有变化
    • The main package must be changed when applying the patch package, otherwise the plugin already loaded error may occur. It is recommended to use -ldflags="-X main.HotfixVersion=v1.0.1" to specify the version number to ensure that the patch package is compiled every time There will be changes
    • 该方案处于实验性质,尚未经过严格验证
    • The solution is experimental in nature and has not yet been rigorously validated

    Example

    参考这个例子项目
    Refer to this example project

    13 条回复    2024-08-29 14:23:54 +08:00
    wswch4444
        1
    wswch4444  
       16 天前
    看着不错,学习下
    Nitroethane
        2
    Nitroethane  
       16 天前
    用 plugin 标准库的话项目得编译成动态链接的 ELF ,就这一点已经很难受了
    limpo
        3
    limpo  
    OP
       16 天前
    @Nitroethane 不用拆分代码,原来编译成 exe 的项目,只需要额外加一个编译成 plugin 就行,可以参考 demo ,不需要做任何代码的拆分,直接编译 plugin 就行
    mshadow
        4
    mshadow  
       16 天前 via Android
    很好,我用 k8s 滚动发布
    Nitroethane
        5
    Nitroethane  
       16 天前
    @limpo #3 没懂我的意思。用 Go 的项目一般都是静态链接,一个 ELF 扔上去完事儿。你这个工具用到了 plugin 标准库,用这个工具的话项目必须得动态链接。你可以把 example 编译成静态链接的 ELF ,然后再试试能不能 hotfix 。
    azraeljack
        6
    azraeljack  
       16 天前
    按照之前玩儿 go plugin 的经验,这东西还是有点难用的,加载之后不能卸载 + 出问题不好排查。而且还有一个问题就是,monkey patch 这东西原作者 license 里明确说了不授权任何人使用,这么搞没版权问题么。
    azraeljack
        7
    azraeljack  
       16 天前
    点了下帖子里的 monkey patch 链接是跳到了 gohook ,看来不是用的之前我看到过的那个 monkey patch 的库。
    limpo
        8
    limpo  
    OP
       16 天前
    @azraeljack 目前的默认实现是 gohook 也支持使用自定义的 patch 库
    limpo
        9
    limpo  
    OP
       16 天前
    @Nitroethane 参考这个脚本 https://github.com/go-hotfix/hotfix/blob/main/example/webapp/run_webapp.sh

    # 这里静态编译主程序
    echo "build webapp..."
    go build -gcflags=all=-l -ldflags="-X main.HotfixVersion=main" -o webapp .

    echo "please modify v1 plugin, press enter key to continue..."
    read input

    # 这里是编译为 so 动态库( plugin )
    echo "build webapp plugin v1..."
    go build -gcflags=all=-l -buildmode=plugin -ldflags="-X main.HotfixVersion=v1" -o webapp_v1.so .

    不知是否是你说的这种情况
    limpo
        10
    limpo  
    OP
       16 天前
    @azraeljack 确实如此,缺点和限制都很多,除了必要情况下一般也不会去使用它
    limpo
        11
    limpo  
    OP
       16 天前
    @mshadow k8s 不错,特别是无状态应用几乎可以做到无感更新,但是对于一个长期运行的有状态服务器,比如游戏服务器,k8s 通常不是首选运行环境,业界通常利用 ab/滚动更新的方式来实现更新,但是这种方式更新非常的慢,通常需要以小时为单位计,如果有刷装备/金币的 bug 等到几个小时恐怕全服玩家都刷个遍了 🤣
    Nitroethane
        12
    Nitroethane  
       16 天前
    @limpo #9 你确定这样编译出来的主程序是静态链接的?用 file 命令确认下吧。如果真是静态链接的,调 `plugin.Open()` 的时候会报 `plugin: not implemented` 错误的。
    limpo
        13
    limpo  
    OP
       16 天前
    @Nitroethane 你的意思是关闭 cgo ,那肯定的都用 plugin 了 cgo 是需要启用的不然肯定无法加载插件了,这个方案也是要求启用 cgo ,因为本质上 plugin open 就是调用 dlopen 加载的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4106 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 09:56 · PVG 17:56 · LAX 02:56 · JFK 05:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.