本文转载自微信公众号《我的脑子是炸鱼》,作者陈建宇。转载本文请联系脑筋急转弯公众号。大家好,我是炸鱼。在Go语言中,有一个功能看似很好用,但却很少被提及,那就是GoPlugin。目前在Go项目中应用并不广泛,覆盖率不高。关于Go问题的吐槽很多,甚至感觉有点冷。前段时间,小鲜鱼的同事问他这个功能怎么用。他正想丢出一个链接,却发现建宇并没有写。不行,Go知识板块的文章图一定要补全。今天在建语,大家一起学习GoPlugin,看看为什么感觉“有点冷”,打开来看看问题出在哪里。WhatisGoTeam在Go1.7开始尝试,在Go1.8正式引入了GoPlugin的机制。2016年发布,一开始只支持Linux实现:GoPlugin机制实现了Go插件的加载和符号解析,可以支持将我们编写的Go包编译成共享库(.so)。这样Go项目就可以加载编译好的GoPlugin(已经成为一个共享库文件),并在程序中调用共享库中的函数、常量、变量等。在Go语言中也称为热插拔插件系统。从Go1.17开始,GoPlugin只支持在Linux、FreeBSD和MacOS上运行,不支持Windows。为什么Go语言需要是静态语言?通常,我们会从以下两个角度来编写程序:从代码编写的角度:我们在编写程序的时候就已经确定了所有的功能,不会出现这种情况。多么彻底的改变啊。从程序的角度看:Go在编译时,所有引用的标准库、第三方库等都已经编译打包成二进制文件,所以无法在运行时动态加载,也就没有办法有其他的可能了。那么为什么需要GoPlugin呢?原因如下:插件可插拔:程序可以随时安装和卸载插件,以获得更多的运行时定制能力。可动态加载的运行时模块:如果你随时安装一个插件,你自然会需要一个可以决定运行哪个插件的模块。插件和模块可以独立开发:主系统和子插件可以由不同的团队开发和提供,这也更有价值。其实本质上,我们还是希望程序能够在运行时实现动态外部加载,根据不同的条件和场景加载不同的插件功能。通用概念如何使用Go官方给出的例子很简单,只需要在编译Go时指定为插件即可。示例构建命令如下:gobuild-buildmode=plugin当插件第一次打开时,所有还不是程序一部分的包的初始化函数都会被调用。但是主函数没有运行。需要注意的是,一个插件只会初始化一次,不能关闭插件。它具有以下API:typePluginfuncOpen(pathstring)(*Plugin,error)func(p*Plugin)Lookup(symNamestring)(Symbol,error)typeSymbolPlugin.Open:打开一个Go插件。如果一个路径已经打开,那么现有的*Plugin将被返回。Plugin.Lookup:在插件中搜索按名称传入的符号。符号是任何导出的变量或函数。如果找不到该符号,它将报告错误。主要细分为插件和符号。Symbol本身就是一个接口。调用Plugin相关方法后,需要进一步断言才能使用。在实际编写并了解基本定义之后,我们定义一个插件。一般我们会有一个plugins/目录作为主程序的一组附属插件。插件代码如下:packagemainimport"fmt"varVintfuncF(){fmt.Printf("我的大脑输入了%d条炸鱼\n",V)}包名必须是main,在根目录下运行插件目录:gobuild-buildmode=plugin-oplugin.somain.go,可以看到编译目录下多了一个plugin.so文件,就是这个插件编译后的动态库.so文件。然后在主程序中加载插件即可,如下:import("plugin")funcmain(){p,err:=plugin.Open("plugin.so")iferr!=nil{panic(err)}v,err:=p.Lookup("V")iferr!=nil{panic(err)}f,err:=p.Lookup("F")iferr!=nil{panic(err)}*v.(*int)=999f.(func())()}输出结果:脑炸鱼999次在程序中,我们首先调用plugin.Open方法打开之前编译好的plugin.so动态库。调用plugin.Lookup方法后立即定位到变量V和方法F,但是由于它们的返回值都是Symbol(接口),所以我们需要对它们进行类型断言后才能调用使用任何时候。至此,一个插件的基本使用就完成了。为什么不需要?前面我们提到了大量的GoPlugins的优点,也展示了编写Plugin代码是多么的简单和方便。但是,为什么GoPlugin已经发布4年了,仍然没有被大规模使用,甚至很多业务发展不需要它,或者根本不为人所知?广泛的应用至少要满足以下三点:基数:需要的场景非常多。入门:方便易用。质量:无大问题。比较郁闷的是GoPlugin的三大要点还不够,导致这个功能没有大规模应用。例如GoPlugin的应用有以下限制:环境问题:不支持Windows等(暂无计划,#19282),MacOS有一些问题,一开始只支持Linux,其他支持会逐步加入之后。Go版本问题:插件构建环境和第三方包的依赖版本需要保持一致。特性问题:缺少Plugin特性,比如不支持关闭插件,也没有新计划支持(#20461)。总结在围棋问题游泳的时候,可以看到很多朋友4年来踩过的坑和无奈。甚至有一个高度赞扬的答案(#19282)说:插件功能主要是一个技术演示,出于某种邪恶的原因作为语言的稳定功能发布。)。目前GoPlugin不是GoTeam的优先事项,并且Windows/Mac支持存在问题。GOPATH有问题,GO版本不同也有问题。更推荐如果你想要插件,走慢一点的grpc路线,因为它们是有效的插件。也可以参考一些GoPlugin用户的解决方案,比如tidb,甚至写了一个guide文档。但是如果你想在生产中正式使用它,我劝你慎重考虑,或者等待……更完美的一天?参考GoPackagepluginWhyisthereisthenowindowssupportforplugins?plugin:addWindowssupportplugin:Addsupportforclosingplugins如何评估Go标准库中的新插件包?一篇文章看懂Go语言的插件
