Go 插件机制

Go 语言的插件机制依赖于动态库(或者叫「共享对象」)实现,让 Go 程序可以在运行时从动态库加载模块,而不是像静态库那样需要静态链接。

注:以 Linux 为例,底层使用了 dlopen / dlsym 等命令。

使用

编译

go build 命令中添加 -buildmode=plugin 可以编译出 Go 插件。编译后会得到 .so 文件。

为了让调用方使用,插件代码中可以实现变量、函数和结构体等(需要和调用方约定好协议)。

加载 & 调用

通过 plugin 库可以加载 Go 插件(.so 文件)。

其中:

  • Open 可以指定 .so 文件的路径,注册插件并得到 Plugin 结构体(可理解为动态库的句柄)。

  • 通过 Plugin 结构体的 Lookup 方法,可以获得动态库句柄中的特定符号 Symbol

  • Symbol 是一个指针,指向变量或函数。并且,可以转换为变量或函数。

举例

举例说明整体流程:

  1. 调用方和插件方确定好调用的协议,比如一个 Go 接口。

  2. 插件方实现一个结构体变量,结构体实现了对应的接口。

  3. 编译插件,得到 .so 文件。

  4. 调用方通过 Open 加载插件 .so 文件,然后调用 Lookup 方法,指定实现了结构的变量名(在插件代码中定义),得到 Symbol

  5. Symbol 转换为接口变量,此后可以通过接口调用插件的逻辑。

插件机制的应用场景

通过插件机制,主程序可以实现动态加载和更新模块(可理解成一段代码逻辑),而不用编译或重启主程序。

如果需要热更新,只需要实现多个 .so 文件的动态加载和切换即可。

插件机制的问题

  • 系统限制:目前只在 Linux、Darwin 和 FreeBSD 上支持,在 Windows 上没有支持。

  • Go 版本限制:插件和主程序需要用同一个 Go 版本编译(编译参数也要一样),精确到小版本(如 1.20.14)。

  • 依赖库限制:如果插件和主程序依赖了同一个库,两者用的库版本需要一样。

  • 关闭机制:插件没有提供 Close 方法。如果不主动清理,插件创建的内存和 Goroutine 资源会一直存在(直至主程序退出)。

  • 调试:在 Debug Go 程序时,无法跳转到插件内部的代码。

Updated: