写一个最简单的插件
在实践中学习,动动手指
ch.go
代码语言:javascript复制package main
import "common/greeterinterface"
type ChineseGreeter struct {
}
func (g *ChineseGreeter) Greet() string {
return "世界你好"
}
// 这个就是可导出的函数,插件在lookup这个函数
func NewGreeter() greeterinterface.Greeter {
return new(ChineseGreeter)
}
编译插件
代码语言:javascript复制go build -buildmode=plugin -o ./ch.so ch.go
在主程序中载入并运行插件
代码语言:javascript复制package plug
import (
"plugin"
"common/greeterinterface" // 主程序中,仍然引用了这个接口,它是对插件的桥梁
"log"
)
func Load() (err error) {
// 打开插件
plug, err := plugin.Open("./ch.so")
if err!=nil{
log.Fatalf("load plugin failed:%v", err)
}
// 寻找对象
greeterObj, err := plug.Lookup("NewGreeter")
if err!=nil{
log.Fatalf("look up failed :%v", err)
}
// 转换对象的格式
creator := greeterObj.(func() greeterinterface.Greeter)
greeter := creator() // 此时调用的是插件中的函数
greeter.Greet() // 创建的结构和直接写代码new的无区别
return nil
}
几点建议
太棒了,现在你已经获得了写插件的新技能。这里有几点实现插件的不成熟建议:
- 插件需要定义在main包。按通常的方式编写函数和结构体即可。
- 定义接口在第三方的module中,插件实现这个接口。比如下面的
common/greeterinterface
。 - 在插件中定义构造函数,如下面的
NewGreeter
。 - 在主程序中,引用插件中的构造函数即可创建对象。
有哪些坑
- go的插件只支持open,不支持close。这样会有内存泄露。
- go的插件,相同的module name只支持加载一次。
- 一定要使用go.mod进行项目管理,插件和主程序的所有相同的依赖版本要相同,建议用主程序的go.mod内容同步到插件的go.mod。否则在open的时候将直接报错。
- 测试发现,调用无业务的Greet函数,原生 go 比插件性能快5倍,但只有纳秒级的区别。原生go执行1000万次函数耗时3ms,插件耗时 16ms。 而执行带业务的函数,性能差距则并不明显。
为什么要使用插件
- 隐藏业务的代码实现。有时候多部门合作时,需要隐藏业务的实现。
- 平台方提供平台主程序和接口协议,具体业务由不同的部门实现。类似Linux的版本是开源的,但ko的实现则不一定。
- 插件和主程序共同引用的第三方包版本必须完全相同,这里为协作带来了不小的麻烦。
- 分离业务实现,避免频繁的编译主程序框架。显然,将业务以文件的形式分离提供了更大的灵活性,但这种灵活性在实践中有这几个问题:
- 插件的体积很大。因为go的特点,每个插件so占用的内存和磁盘在20M以上。
- 版本管理不方便。so的版本以文件方式会有管理成本。
- 主程序仍然需要重启加载插件。因为插件只能Load而不能Close,且不能重复加载,目前没有很好的动态加载/卸载插件的方法。
总体来说,plugin 包还有相当大的提升空间,但这似乎并不是go团队的关注重点。