golang实现代码热重载无需重启服务器的插件库hotswap的使用

Golang实现代码热重载无需重启服务器的插件库hotswap使用指南

Banner

Hotswap提供了一个完整的解决方案,可以无需重启服务器就能重新加载Go代码,且不会中断或阻塞任何正在进行的流程。它基于Go的插件机制构建。

主要特性

  • 轻松重新加载代码
  • 完全隔离地运行不同版本的插件
  • 像往常一样使用分层包结构
  • 使用Plugin.InvokeFunc()从主程序调用插件内函数
  • 通过PluginManager.Vault.Extension和/或PluginManager.Vault.DataBag公开插件数据与函数
  • 使用live functionlive typelive 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重新加载其插件。

  1. hello展示基本用法,包括如何组织主机和插件,如何构建它们,如何在服务器启动时加载插件,如何使用InvokeEach,以及如何重新加载。
  2. extension展示如何定义自定义扩展以及如何使用PluginManager.Vault.Extension
  3. livex较为复杂,展示如何使用live functionlive typelive data
  4. slink是插件静态链接的示例,这使得在MacOS和Windows上使用调试器(delve)调试插件成为可能。
  5. 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 functionlive typelive data来避免这个问题
  • 主程序的代码应该从不导入任何插件的包,插件的代码应该从不导入其他插件的包
  • 由于golang插件的限制,旧版本不会从内存中移除。但是Hotswap提供了OnFree函数来清除缓存
  • 需要使用gitgo 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

1 回复

更多关于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"
    }),
)

热重载流程

  1. 修改插件代码
  2. 重新编译插件:go build -buildmode=plugin -o plugin/hello/hello.so plugin/hello/hello.go
  3. hotswap 会自动检测到变更并重新加载插件

注意事项

  1. 插件必须使用 -buildmode=plugin 编译
  2. 主程序和插件必须使用相同版本的 Go 编译
  3. 插件和主程序的依赖版本必须一致
  4. 热重载不适合所有场景,对于数据库连接等资源需要特别处理

总结

hotswap 提供了一种优雅的方式在 Go 中实现热重载功能,特别适合需要长期运行且需要频繁更新的服务。通过合理设计插件架构,可以实现业务逻辑的动态更新而无需重启服务。

实际使用时,建议结合 CI/CD 流程自动编译和部署插件,并添加适当的监控和回滚机制以确保服务稳定性。

回到顶部