golang实现代码热重载无需重启服务器的插件库hotswap的使用
Golang实现代码热重载无需重启服务器的插件库hotswap使用指南
Hotswap
提供了一个完整的解决方案,可以无需重启服务器就能重新加载Go代码,且不会中断或阻塞任何正在进行的流程。它基于Go的插件机制构建。
主要特性
- 轻松重新加载代码
- 完全隔离地运行不同版本的插件
- 像往常一样使用分层包结构
- 使用
Plugin.InvokeFunc()
从主程序调用插件内函数 - 通过
PluginManager.Vault.Extension
和/或PluginManager.Vault.DataBag
公开插件数据与函数 - 使用
live function
、live type
和live data
处理异步作业 - 静态链接插件以便调试
- 使用
Export()
向其他插件公开函数 - 使用
Import()
依赖其他插件
快速开始
安装hotswap命令行工具:
go install github.com/edwingeng/hotswap/cli/hotswap@latest
从源代码构建插件
Usage:
hotswap build [flags] <pluginDir> <outputDir> -- [buildFlags]
Examples:
hotswap build plugin/foo bin
hotswap build -v plugin/foo bin -- -race
hotswap build --staticLinking plugin/foo plugin
Flags:
--debug 启用调试模式
--exclude string 要排除的文件的正则表达式
--goBuild 如果--goBuild=false,则跳过go build过程(默认true)
-h, --help 显示帮助
--include string 除了.go文件外,要包含的文件的正则表达式
--leaveTemps 不删除临时文件
--livePrefix string live函数和live类型的名称前缀(不区分大小写)(默认"live_")
--staticLinking 生成静态链接代码而不是构建插件
-v, --verbose 启用详细模式
示例Demo
可以在demo
目录中找到这些示例。要直接体验,可以使用run.sh
启动服务器,并使用reload.sh
重新加载其插件。
hello
展示基本用法,包括如何组织主机和插件,如何构建它们,如何在服务器启动时加载插件,如何使用InvokeEach
,以及如何重新加载。extension
展示如何定义自定义扩展以及如何使用PluginManager.Vault.Extension
。livex
较为复杂,展示如何使用live function
、live type
和live data
。slink
是插件静态链接的示例,这使得在MacOS和Windows上使用调试器(delve)调试插件成为可能。trine
是最后一个示例,展示了插件依赖机制。
必需函数
插件必须在其根包中定义以下函数:
// OnLoad在所有插件成功加载后调用,在Vault初始化之前
func OnLoad(data interface{}) error {
return nil
}
// OnInit在所有OnLoad函数执行后调用。此时Vault已准备就绪
func OnInit(sharedVault *vault.Vault) error {
return nil
}
// OnFree在重新加载后的某个时间调用
func OnFree() {
}
// Export返回要导出到其他插件的对象
func Export() interface{} {
return nil
}
// Import返回表示插件依赖关系的对象
func Import() interface{} {
return nil
}
// InvokeFunc调用指定函数
func InvokeFunc(name string, params ...interface{}) (interface{}, error) {
return nil, nil
}
// Reloadable表示插件是否可重新加载
func Reloadable() bool {
return true
}
插件重新加载时的执行顺序
1. Reloadable
2. Export
3. Import
4. OnLoad
5. Vault Initialization
6. OnInit
注意事项
- 构建主程序时使用环境变量
CGO_ENABLED=1
和-trimpath
标志 - 不要在可重新加载的插件中定义任何全局变量,除非它可以随时丢弃或实际上永远不会改变
- 不要在插件中创建任何长时间运行的goroutine,这容易出错
- 插件不同版本中的相同类型在运行时实际上是不相同的。使用
live function
、live type
和live data
来避免这个问题 - 主程序的代码应该从不导入任何插件的包,插件的代码应该从不导入其他插件的包
- 由于golang插件的限制,旧版本不会从内存中移除。但是
Hotswap
提供了OnFree
函数来清除缓存 - 需要使用
git
和go module
管理代码 - 强烈建议将主程序和所有插件的代码保存在同一个仓库中
Live Things
live function
是名称前缀为live_
(不区分大小写)的函数类型。live函数会自动收集并存储在PluginManager.Vault.LiveFuncs
中。例如:
func live_Foo(jobData live.Data) error {
return nil
}
live type
是名称前缀为live_
(不区分大小写)的结构体类型。live类型会自动收集并存储在PluginManager.Vault.LiveTypes
中。例如:
type Live_Bar struct {
N int
}
live data
是一种类型守卫。在调度异步作业时可以将数据转换为live data
对象,在处理作业时可以从live data
对象恢复数据- 详见
livex
示例
常见问题
如何用调试器调试插件?
使用--staticLink
构建它。更多信息请参考slink
示例。
hotswap
在Windows上能用吗?
使用--staticLink
构建可以在Windows上工作,但由于Go的插件机制不支持Windows,插件重新加载不可用。
更多关于golang实现代码热重载无需重启服务器的插件库hotswap的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现代码热重载无需重启服务器的插件库hotswap的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 实现代码热重载:使用 hotswap 插件库
在 Go 语言中实现代码热重载(无需重启服务器)可以使用 hotswap 这个优秀的插件库。下面我将详细介绍如何使用 hotswap 实现这一功能。
hotswap 简介
hotswap 是一个 Go 语言的插件热加载框架,它允许你在运行时动态加载、卸载和替换 Go 插件,而无需重启主程序。这对于需要长期运行且需要频繁更新业务逻辑的服务非常有用。
安装 hotswap
首先安装 hotswap:
go get github.com/edwingeng/hotswap
基本使用示例
1. 项目结构
myapp/
├── main.go # 主程序
├── plugin/ # 插件目录
│ ├── hello/ # hello 插件
│ │ ├── go.mod
│ │ ├── hello.go
│ │ └── plugin.json
│ └── ... # 其他插件
└── go.mod
2. 主程序代码 (main.go)
package main
import (
"fmt"
"log"
"time"
"github.com/edwingeng/hotswap"
"github.com/edwingeng/hotswap/demo/hello/g"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
// 创建 hotswap 管理器
swapper := hotswap.NewPluginManagerSwapper(
"plugin/", // 插件目录
hotswap.WithLogger(g.Logger),
hotswap.WithFreeDelay(time.Second*5),
)
// 初始加载插件
if err := swapper.LoadPlugins(); err != nil {
log.Fatal(err)
}
// 定期检查并重新加载插件
go func() {
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
for range ticker.C {
swapper.ReloadIfNeeded()
}
}()
// 使用插件功能
for i := 0; i < 10; i++ {
if v, ok := g.PluginManagerSwapper.Current().Invoke("hello", "Greet"); ok {
fmt.Println(v[0].(string))
}
time.Sleep(time.Second)
}
}
3. 插件代码 (hello/hello.go)
package hello
import (
"github.com/edwingeng/hotswap"
)
func OnLoad(data interface{}) error {
return nil
}
func OnInit(sharedVars map[string]interface{}) error {
return nil
}
func OnFree() error {
return nil
}
func Greet() string {
return "Hello, World! (from plugin)"
}
// Export 导出插件函数
var Export = map[string]interface{}{
"Greet": Greet,
}
var Vault = hotswap.NewVault()
4. 插件描述文件 (hello/plugin.json)
{
"name": "hello",
"importpath": "github.com/yourname/myapp/plugin/hello",
"revision": "1.0.0",
"build": {
"main": "hello.go"
}
}
5. 插件 go.mod 文件 (hello/go.mod)
module github.com/yourname/myapp/plugin/hello
go 1.16
require github.com/edwingeng/hotswap v1.0.0
高级特性
1. 插件间共享数据
// 在主程序中设置共享数据
swapper := hotswap.NewPluginManagerSwapper(
"plugin/",
hotswap.WithLogger(g.Logger),
hotswap.WithSharedVars(map[string]interface{}{
"config": config,
}),
)
// 在插件中访问共享数据
func OnInit(sharedVars map[string]interface{}) error {
if cfg, ok := sharedVars["config"]; ok {
// 使用配置
}
return nil
}
2. 插件依赖管理
在 plugin.json 中添加 dependencies 字段:
{
"name": "pluginB",
"importpath": "github.com/yourname/myapp/plugin/pluginB",
"revision": "1.0.0",
"dependencies": ["pluginA"],
"build": {
"main": "pluginB.go"
}
}
3. 自定义插件加载逻辑
swapper := hotswap.NewPluginManagerSwapper(
"plugin/",
hotswap.WithLogger(g.Logger),
hotswap.WithPluginFilter(func(plugin *hotswap.PluginSpec) bool {
// 只加载特定条件的插件
return plugin.Name == "hello"
}),
)
热重载流程
- 修改插件代码
- 重新编译插件:
go build -buildmode=plugin -o plugin/hello/hello.so plugin/hello/hello.go
- hotswap 会自动检测到变更并重新加载插件
注意事项
- 插件必须使用
-buildmode=plugin
编译 - 主程序和插件必须使用相同版本的 Go 编译
- 插件和主程序的依赖版本必须一致
- 热重载不适合所有场景,对于数据库连接等资源需要特别处理
总结
hotswap 提供了一种优雅的方式在 Go 中实现热重载功能,特别适合需要长期运行且需要频繁更新的服务。通过合理设计插件架构,可以实现业务逻辑的动态更新而无需重启服务。
实际使用时,建议结合 CI/CD 流程自动编译和部署插件,并添加适当的监控和回滚机制以确保服务稳定性。