典型场景-微内核架构
可阅读去哪儿网的落地实践分享。
从上到下隔离级别逐渐递增,灵活性逐渐增高,性能逐渐下降,因此在选型时需要考虑插件是否是高频调用,耗时要求如何;
Go 语言落地实现
如上述可知,去哪儿网采用了 Spring 生态下提供的运行时插件设计,是基于 java 语言的实现,以下给出几种基于 Go 语言的实现方案;
编译时插件 - 代码级别的隔离
使用引包的方式,进行依赖注入,类似于 go 标准库中的 database/sql;
运行时插件
plugin
原理可见:GO 语言设计与实现 8.1 插件系统,利用了操作系统的动态库链接能力实现模块化的设计,go/plugin -> cgo -> c -> 操作系统
关于详细内容可见 一文搞懂Go语言的plugin,在此做摘要介绍:
问题
有如下问题,因此并未被广泛使用
- 官方只明确说支持 Linux、FreeBSD 和 macOS,因此其他系统是否支持不能确定,且即使可以使用,会不会有潜藏风险无从得知;
- 当一个插件第一次被open时,plugin中所有不属于主程序的包的init函数将被调用,但一个插件只被初始化一次,而且不能被关闭;
- 若主程序和插件包依赖同一第三方库,则该共同依赖包的版本必须一致;
- 若主程序或插件库任一一方采用 vendor 构建,则主程序和插件包必须基于同一个 vendor 目录构建;
- 主程序和插件库使用的编译器的版本必须一致;
- 使用插件库的主程序只能使用动态链接,不能使用静态链接;
- 关于插件的版本,若插件名相同,内容也相同,主程序多次 load 不会出现问题;但若插件名相同,但内容不同,主程序运行时多次 load 会导致无法恢复的 panic。因此插件版本管理需要注意。
解法
根据上面看到的种种约束,如果要应用go plugin,必须要做到:
- 构建环境一致
- 对第三方包的版本一致。
因此,业内在使用go plugin时多利用builder container(用来构建程序的容器)来保证主程序和plugin使用相同的构建环境。
在go plugin为数不多的用户中,有三个比较知名的开源项目值得后续认真研究:
- gosh: https://github.com/vladimirvivien/gosh
- tyk api gateway: https://github.com/TykTechnologies/tyk
- tidb : https://github.com/pingcap/tidb,并给出了其插件系统使用go plugin的完整设计方案:https://github.com/pingcap/tidb/blob/master/docs/design/2018-12-10-plugin-framework.md
远端插件 - 进程级别隔离
hashicorp/go-plugin
https://github.com/hashicorp/go-plugin
小结
实例间无差别部署,通过回环地址或 unix 域套接字实现本地插件访问,每个机器上内部署相同的插件,归为远端插件,实际仍是本地插件方案;
问题
- reattach (服务重启重新加载插件)需要业务代码自己记录下插件的pid和addr,才能实现;
- 只实现了单个插件的加载,没有实现多个插件的管理,如果要管理多个插件的话,还是需要业务代码里自行去组织;
- 并不是一套完整的本地插件解决方案,只实现了基本的 server 与插件的交互逻辑,在插件的管控上仍然需要自行设计、管理。
其他方案
另有一种未开源方案,基于 ZK 进行插件的中心化管控,不同的机器上部署有不同的插件,实现插件的有差别部署。业务服务从 ZK 中取到插件 IP、Port 信息通过 grpc 协议进行访问,是真正的网络插件。
总结
对 Go 来说远端插件的出现很大程度上是为了弥补运行时插件的不足,实现了灵活性的同时,却一定程度上降低了性能。虽然使用 Unix 域套接字相对于回环地址可以提升性能(见此),但其性能仍低于运行时插件调用(见此:二 Go call Java)。
参考
https://www.bilibili.com/video/BV1qr4y1S7ZJ?spm_id_from=333.9...
https://tonybai.com/2021/07/19/understand-go-plugin/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。