很多年以前我就开始写一系列关于插件的文章:介绍这些插件在不同的系统和编程语言下是如何设计和实现的。今天这篇文章,我打算把这个系列扩展下,讲讲 Go 语言中一些插件的例子。

需要提醒的是,本系列头几篇的文章就介绍了插件的四个基本概念,并且声明几乎所有的插件系统,都可以将它们的设计映射到以下 4 个概念来描述和理解:

  1. 发现

  2. 注册

  3. 插件附着到应用程序上的钩子(又称,” 挂载点 “)

  4. 将应用程序能力暴露给插件(又称,扩展 API)

Gopher holding an Ethernet cable plugged into the wall

两种类型插件

和其他静态编译编程语言一样,Go 中通常会讨论两种一般类型的插件:编译时插件和运行时插件。这两种我们都会讲到。

编译时插件

编译时插件由一系列代码包组成,这些代码包编译进了应用程序的二进制文件中。一旦二进制文件编译好,它的功能就固定了。

最有名的 Go 编译时插件系统就是 database/sql 包的驱动程序。我已经写了一整篇关于这个话题的文章,大家可以看下。

简单概括下:数据库驱动是主应用程序通过一个空白导入 _ "name" 导入的包。这些包通过它们的 init函数使用sql.Registerdatabase/sql注册。

关于基本插件的概念, 下面有一个编译时插件如何运作的例子(以database/sql为例)

  1. 发现:这点很明确,import一个插件包。插件可以在它们init函数自动执行注册。

  2. 注册:由于插件被编译到主应用程序之中,它可以直接从插件中调用一个注册函数 (例如 sql.Register)。

  3. 应用程序钩子:通常,插件将实现应用程序提供的接口,注册过程将连接接口实现。插件使用database/sql实现驱动程序。驱动程序接口和实现该接口的值将使用 sql.Register 注册。

  4. 将应用程序能力暴露给插件:对于编译时插件,这很简单;由于插件被编译成二进制文件,它可以从主应用程序中导入实用程序包,并根据需要在代码中使用它们。

运行时插件

运行时插件的代码不会被编译到主应用程序的原始二进制文件中;相反,它在运行时连接到这个应用程序。在编译语言中,实现这一目标的常用工具是共享库。Go 也支持这种方法。本节的后面部分将提供一个使用共享库,在 Go 中开发插件系统的例子;最后还会讨论其他方式实现的运行时插件。

Go 自带一个内置在标准库中的插件包。这个包让我们可以写出编译进共享库,而不是可执行二进制文件的 Go 程序。另外,它还提供了简单函数来从插件包里面加载共享库和获取符号。

在这篇文章中,我开发了一个完整的运行时插件系统示例;它复制了之前关于插件基础设施的文章中的htmlize源码,并且它的设计和后面那篇C 语言中的插件文章类似。这个示例程序很简单,就是把一些标记语言(比如 reStructuredText 或者 Markdown)转换成 HTML,并支持插件,使得我们能够调整某些标记元素的处理方式。完整的示例代码在这篇文章里。

Directory contents of the plugin sample

让我们用插件的基本概念来分析这个例子。

发现和注册:是通过文件系统查找完成。主应用程序有一个带有LoadPlugins函数的插件包。这个函数扫描给定目录中以.so 结尾的文件,并将所有此类文件视为插件。它希望在每个共享库中找到一个名为InitPlugin的全局函数,并调用它,为它提供一个PluginManager(稍后会详细介绍)。

插件最开始是怎么变成.so文件的呢?通过 命令 -buildmode=plugin 构建。具体更多的细节,可以看示例源码 中的buildplugins.sh脚本和 README 文件。

应用程序勾子:现在是描述PluginManager类型的好时机。这是插件和主应用程序之间通信的主要类型。流程如下:

  • 应用程序在 LoadPlugins 新建一个 PluginManager,并将其传给它找到的所有插件。

  • 每个插件使用PluginManager来给各种勾子注册自己的处理程序。

  • LoadPlugins 在所有的插件注册后,将PluginManager返回给主程序。

  • 当应用程序运行时,使用  PluginManager 来根据需要调用已注册插件的勾子。举个例子,PluginManager 有下面这个函数:

func (pm *PluginManager) RegisterRoleHook(rolename string, hook RoleHook)

RoleHook 是一个函数类型:

// RoleHook takes the role contents, DB and Post and returns the text this role
// should be replaced with.
type RoleHook func(string, *content.DB, *content.Post) string

插件可以调用RegisterRoleHook 来注册一个特定文本角色的处理程序。请注意,尽管这个设计并没有使用 Go 的 interfaces ,但是其他设计也可以实现同样功能,取决于应用程序的具体情况。

将应用程序能力暴露给插件:正如上面 RoleHook 类型那样,应用程序将数据对象传递给插件使用。content.DB 提供了对应用程序数据库的访问。content.Post 提供了当前格式化插件的特定的 Post。插件可以根据需要使用这些对象,来获取应用程序的数据或者行为。

运行时插件的替代方法

考虑到插件包只是在 Go1.8 中新增的,还有前面描述的种种限制,所以也难怪 Go 生态系统中出现了其他插件方法。

其中最有趣的一个方向就是,IMHO,通过 RPC 调用插件。我一直很喜欢将应用程序解耦到独立的进程中,然后通过 RPC 或本地主机上的 TCP 进行通信。(我猜他们现在称之为 微服务),因为它有几个重要的优点:

  • 隔离性:插件的崩溃不会导致整个应用程序崩溃。

  • 语言之间的交互性:如果 RPC 是接口,你还会在乎插件使用什么语言写的吗?

  • 分布式:如果插件通过网络接口,我们可以很容易将它们分发到不同机器上,来提高性能、可靠性等等。另外,Go 标准库中有一个很强大的 RPC 包:net/rpc,让这一点实现起来相当容易。

最广泛使用的基于 RPC 的插件系统就是hashicorp/go-plugin,Hashicorp 以创建优秀的 Go 软件而闻名,显然他们在许多系统中使用了 Go 插件,因此这些插件都是经过实战测试过的。(尽管他们的文档可以写的更好点)

Go 插件运行在 net/rpc 之上,当然也支持 gRPC。像 gRPC 这样的高级 RPC 协议非常适合插件,因为它们包含了开箱即用的版本控制,解决了不同版本的插件与主应用程序之间的互操作性问题。

原文地址:

https://eli.thegreenplace.net/2021/plugins-in-go/

原文作者:Eli Bendersky

本文永久链接:https://github.com/gocn/translator/blob/master/2021/w35-Plugins%20in%20Go.md

译者:朱亚光

校对:

想要了解关于 Go 的更多资讯,还可以通过扫描的方式,进群一起探讨哦~

『每周译Go』Go 语言中的插件相关推荐

  1. 『每周译Go』Go 语言的 goroutine 性能分析

    本文档最后一次更新时所用的 Go 版本是 1.15.6,但是大多数情况下,新老版本都适用. 描述 Go 运行时在一个称为 allgs 简单切片追踪所有的 goroutines.这里面包含了活跃的和死亡 ...

  2. 『每周译Go』谈谈 Go 中的内存

    今天的文章来自于最近的 Go 代码测试.请看下面的基准测试代码.1 func BenchmarkSortStrings(b *testing.B) {s := []string{"heart ...

  3. 『每周译Go』开启并发模式

    在这篇文章中,我将介绍在 Go 中使用基本并发模式和原生原语来构建并发应用程序的一些最佳实践.模式本身适用于任何语言,但对于这些示例,我们将使用 Go. 可以下载本文的源码配合阅读. git clon ...

  4. 『每周译Go』Google:12 条 Golang 最佳实践

    这是直接总结好的 12 条,详细的再继续往下看: 先处理错误避免嵌套 尽量避免重复 先写最重要的代码 给代码写文档注释 命名尽可能简洁 使用多文件包 使用 go get 可获取你的包 了解自己的需求 ...

  5. 『每周译Go』Rust 与 Go: 为何相得益彰

    虽然有一些人可能会将 Rust 和 Go 视为互为竞争的编程语言,但 Rust 和 Go 团队都不这么认为.恰恰相反,我们的团队非常尊重其他人正在做的事情,并将这些语言视为对整个软件开发行业现代化的共 ...

  6. 『每周译Go』那些年我使用Go语言犯的错

    原文地址:https://henvic.dev/posts/my-go-mistakes/ 原文作者:Henrique Vicente 本文永久链接:https://github.com/gocn/t ...

  7. 『每周译Go』Go sync map 的内部实现

    目录 引言 a. 简单介绍并发性及其在此上下文中的应用 sync.RWMutex 和 map 一起使用的问题 介绍 sync.Map a. 在哪些场景使用 sync.Map? sync.Map 实现细 ...

  8. 『每周译Go』写了 50 万行 Go 代码后,我明白这些道理

    原文地址:https://blog.khanacademy.org/half-a-million-lines-of-go/ 原文作者:Kevin Dangoor 本文永久链接:https://gith ...

  9. 『每周译Go』GitHub 为 Go 社区带来供应链安全功能

    Go 国际社区从一开始就拥抱 GitHub ( GitHub 即是 Go 代码协作的地方也是发布包的地方) 使得 Go 成为 如今 GitHub 上排名前 15 的编程语言.我们很高兴地宣布 GitH ...

最新文章

  1. 软件性能测试vu脚本录制,利用LR插件完成性能测试脚本
  2. 线性模型的最小二乘法拟合(转)
  3. Visual Studio查找搜索类和方法的快捷方法
  4. 忘记Oracle中System和Sys密码的解决办法
  5. Spring Security——login显示[Bad credentials]
  6. Linux命令之pstree - 以树状图显示进程间的关系
  7. 亚马逊Simple Worklfow服务的骆驼演示
  8. C++语言 如何用G++进行编译和运行程序
  9. Orange Business Services 全球发布 Easy Go Network,帮助企业加速实现“网络即服务”...
  10. 生物数据库建设,等你来~
  11. 蒋本珊计算机组成原理知识点笔记,计算机组成原理习题答案解析(蒋本珊)
  12. 黑马程序员JAVAWEB教程P141课后练习
  13. 私有服务器虚拟化软件市场排名,操作系统、数据库和虚拟化软件2017年市场格局分析...
  14. 1060显卡支持dx12吗_真香!1060显卡支持光线追踪技术
  15. 下载B站、秒拍等视频网站视频
  16. vue子组件获取祖先组件值的方法
  17. windows server 2012r2 standar 安装 vc++2015失败
  18. 如何使用阿里云服务器快速搭建个人网站?
  19. 数据归档,存储的完美储备军
  20. RT-thread相关教程汇总

热门文章

  1. idea 连接oracle 数据库 要点
  2. PHP字符串计算函数
  3. 今天学到一个新姿势(划掉)知识
  4. py基础---多线程、多进程、协程
  5. 高精度除法算法(大数除于小数)
  6. 修改Win7开机登录界面背景图片
  7. iOS APP图标一键生成
  8. python、前端vue——全栈——vscode插件
  9. 魔兽世界怀旧服哪个服务器金价稳定,魔兽世界:怀旧服金价重回“0.1时代”,稀有材料价格却不降反升...
  10. 高超音速技术行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)