Golang插件使用
在现有的需求中,因业务需求还在扩充,使得前期搜集需求的过程中,很难完整的穷尽所有的处理逻辑。此时,需要一种技术,能够做到后期补充操作逻辑。在完成整体开发后,由其他开发人员进行扩展,由此,其他开发人员将无需更改应用程序的代码,更不至于重新编译整个应用程序。
在Go语言中,已知的插件系统中,可有三种选项:Hashicorp
go-plugin,内置plugins
软件包,以及Go Javascript解释器。
Hashicorp插件
地址:go-plugin。
此项目不同Go本身自带的plugin机制,go-plugin
是基于RPC的Go插件系统。其项目发展比较长,已被大量使用于Hashicorp
自家的各个项目中。(Packer、 Terraform、Nomad、 Vault)star
数量都算是比较高,用于生产可以说没有什么特别的问题。
其必然可以使用可扩展性,但囿于其本身的原理,在实际的使用中,需要必备的几个二进制文件:主体程序的二进制文件、插件的二进制文件。
看一个简单的例子来明白其原理:
主机和插件之间通过一个interface
接口来约定方法。
1 | type Hello interface { |
接下来,调用插件:
1 | // We're a host! Start by launching the plugin process. |
handshakeConfig
为一个结构体,作为host与plugin之间一个简单的通信确认。
1 | var handshakeConfig = plugin.HandshakeConfig{ |
该库还支持使用gRPC
通信,其还可以使用其他语言来编写插件。由于是使用的网络通信,其本体还是单独的程序,所以编程上除开框架的复杂性,其可以正常使用语言所有特性。
该插件的缺点:
- 主程序与插件之间强耦合;(主程序定义接口,插件实现接口。)
- 开发者需要了解简单的rpc以及接口知识;
- 改动插件即需要重新编译;若主程序需要扩展插件逻辑也需要重新改动代码;
Go插件模块
Go本身是支持插件系统的,但在实际使用上来看,可用性不是很高。其具体原因还需要在具体的使用上来谈。
虽然在宣传上,Go插件模块不仅背靠原生支持这块大旗,还具有低于go-plugin的复杂性(不需要借助rpc)。理论上,主程序与插件之间没有直接连接。
但是,使用plugin时,plugin经常要和主程序同时(更确切的说是同一环境下)build才行。如果主程序有改动或者build的路径更换,plugin不同时更新的话,加载plugin时就会报某个package版本错误的问题,导致加载失败。在Go Modules解决方案还未完全普及前,此问题就一直是个问题,所以编译环境需要一直保持统一。
在插件中,实现函数方法:
1 | package main |
在main函数中,为了方便访问,定义插件方法的接口,使用包方法plugin.Open
来解析特定插件。
1 | type Greeter interface { |
然后,解析插件导出的符号,将其转换为接口类型,然后运行该Greet()
方法。
1 | symGreeter, err := plug.Lookup("Greeter") |
此方法使用上比go-plugin
简单,程序开销也小一些。
该插件的缺点:
- 必须先针对特定平台进行编译,才能在运行时加载插件;
- 当前的实现仅支持类似Unix的平台,例如Linux和macOS;
- 只能使用Go,虽然原生支持使得其变得方便了许多,但也没有了
go-plugin
支持多语言的特性;
Go JavaScript
在Go程序运行JavaScript,将JS文件作为Go程序的延申,使其作为主程序的插件来使用。需要使用到库otto。
最关键的是,插件无需进行编译,单纯的JS文件即可。
简单的使用:
1 | package main |
借助此库,我们能运行时解释和运行纯JavaScript(到目前为止,仅限于ECMAScript 5)。
The following are some limitations with otto:
1
2
3
4 > * "use strict" will parse, but does nothing.
> * The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
> * Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.
>
但其强大之处在于,Otto可以使用外部Go函数扩展JS API,还可以双向交换数据。
func (Value) Export
1 | func (self Value) Export() (interface{}, error) |
1 | undefined -> nil (FIXME?: Should be Value{}) |
JS中运行GO函数
在实际使用中,我们可以将Go上实现的函数传递到JS中运行:
1 | package main |
数据交换
将Go程序中的数据注入JavaScript,或从JavaScript函数接收结果也非常简单:
1 | package main |
Run
函数返回两个参数:
1 | func (self Otto) Run(src interface{}) (Value, error) |
Value
结构默认实现了string()
方法,当我们只需要使用返回值的字符串形式,或者确定返回值为string时,可以直接使用。否则,需要Export()
,它将尝试从JS的类型转换成Go类型。
该插件的缺点
- 调试JS代码困难
由于代码写在JS中,Go执行JS代码,所以在调试时会相应的比较麻烦,可以借助在OTTO JAVASCRIPT解释器中使用调试器语句。当然可以现在其他IDE中写完代码后再移植过来会方便一点。
- Otto仅支持ES v5版本。
总结
go-plugin
和otta
均在实际生产中有应用,可使用的场合较之Go内置插件更多。两者有着实质的区别,大体区别如下。
跨平台 | 通信 | 语言 | 编译使用 | |
---|---|---|---|---|
Hashicorp插件 | 多平台 | RPC | Go,支持gRPC | 单独编译 |
Go插件模块 | *nix平台 | 内置 | Go | 单独编译 (需与主程序环境统一) |
Go JavaScript | 多平台 | 第三方支持 | 仅JS v5 | 无需编译 |
本文标题:Golang插件使用
文章作者:小师
发布时间:2021-03-15
最后更新:2022-05-04
原始链接:chunlife.top/2021/03/15/Golang插件使用/
版权声明:本站所有文章均采用知识共享署名4.0国际许可协议进行许可