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