典型场景-微内核架构

可阅读去哪儿网的落地实践分享

image.png
从上到下隔离级别逐渐递增,灵活性逐渐增高,性能逐渐下降,因此在选型时需要考虑插件是否是高频调用,耗时要求如何;

Go 语言落地实现

如上述可知,去哪儿网采用了 Spring 生态下提供的运行时插件设计,是基于 java 语言的实现,以下给出几种基于 Go 语言的实现方案;

编译时插件 - 代码级别的隔离

使用引包的方式,进行依赖注入,类似于 go 标准库中的 database/sql;
image.png

运行时插件

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为数不多的用户中,有三个比较知名的开源项目值得后续认真研究:

远端插件 - 进程级别隔离

hashicorp/go-plugin

https://github.com/hashicorp/go-plugin
image.png

小结
实例间无差别部署,通过回环地址或 unix 域套接字实现本地插件访问,每个机器上内部署相同的插件,归为远端插件,实际仍是本地插件方案;

问题

  1. reattach (服务重启重新加载插件)需要业务代码自己记录下插件的pid和addr,才能实现;
  2. 只实现了单个插件的加载,没有实现多个插件的管理,如果要管理多个插件的话,还是需要业务代码里自行去组织;
  3. 并不是一套完整的本地插件解决方案,只实现了基本的 server 与插件的交互逻辑,在插件的管控上仍然需要自行设计、管理。

其他方案

另有一种未开源方案,基于 ZK 进行插件的中心化管控,不同的机器上部署有不同的插件,实现插件的有差别部署。业务服务从 ZK 中取到插件 IP、Port 信息通过 grpc 协议进行访问,是真正的网络插件。

总结

对 Go 来说远端插件的出现很大程度上是为了弥补运行时插件的不足,实现了灵活性的同时,却一定程度上降低了性能。虽然使用 Unix 域套接字相对于回环地址可以提升性能(见此),但其性能仍低于运行时插件调用(见此:二 Go call Java)。
image.png

参考

https://www.bilibili.com/video/BV1qr4y1S7ZJ?spm_id_from=333.9...
https://tonybai.com/2021/07/19/understand-go-plugin/


Jesse
39 声望8 粉丝