golang 实现加载动态库
golang开发中需要用到插件动态加载技术,需要主程序动态加载一些功能模块,不需要修改主程序。相比于java ,java 可以通过加载class文件方式来实现模块加载,而golang通常都是单个二进制文件。 go-plugin则使用另外一种方式实现插件加载,主进程和插件进程是两个独立的进程,二者通过rpc/grpc的方式进行通信, 从而实现主进程调用插件进程。
其次,golang也原生提供一种插件模块加载机制plugins,下面依次进行介绍。
go-plugin 项目地址https://github.com/hashicorp/go-plugin
特征 插件是Go接口的实现:这让插件的编写、使用非常自然。对于插件的作者来说,他只需要实现一个Go接口即可;对于插件的用户来说,他只需要调用一个Go接口即可。go-plugin会处理好本地调用转换为gRPC调用的所有细节 跨语言支持:插件可以基于任何主流语言编写,同样可以被任何主流语言消费 支持复杂的参数、返回值:go-plugin可以处理接口、io.Reader/Writer等复杂类型 双向通信:为了支持复杂参数,宿主进程能够将接口实现发送给插件,插件也能够回调到宿主进程 内置日志系统:任何使用log标准库的的插件,都会将日志信息传回宿主机进程。宿主进程会在这些日志前面加上插件二进制文件的路径,并且打印日志 协议版本化:支持一个简单的协议版本化,增加版本号后可以基于老版本协议的插件无效化。当接口签名变化时应当增加版本 标准输出/错误同步:插件以子进程的方式运行,这些子进程可以自由的使用标准输出/错误,并且打印的内容会被自动同步到宿主进程,宿主进程可以为同步的日志指定一个io.Writer TTY Preservation:插件子进程可以链接到宿主进程的stdin文件描述符,以便要求TTY的软件能正常工作 宿主进程升级:宿主进程升级的时候,插件子进程可以继续允许,并在升级后自动关联到新的宿主进程 加密通信:gRPC信道可以加密 完整性校验:支持对插件的二进制文件进行Checksum 插件崩溃了,不会导致宿主进程崩溃 容易安装:只需要将插件放到某个宿主进程能够访问的目录即可 用法 这里我们通过一个实例来体验一下go-plugin插件开发。我们需要完成的功能是,主程序暴露了两个方法,插件模块去实现这两个方法,然后主程序加载插件完成功能实现。 最终达成的效果是通过Put方法设置一个key/value对,通过get方法获取key对应的值。
Put(key string, value []byte) error Get(key string) ([]byte, error) ([]byte, error) 新建项目 这里我先将完整项目目录结构列出来
. ├── Makefile ├── cmd │ ├── cmd │ ├── kv_wky │ └── main.go ├── pkg │ ├── plugins │ │ └── proto │ │ ├── plugin.pb.go │ │ └── plugin.proto │ └── shared │ ├── grpc.go │ └── plugin.go └── pluginclient ├── main.go └── pluginclient 定义proto 新建pkg/plugins/proto/plugin.proto文件,并生成plugin.pb.go
syntax = "proto3";package proto;message GetRequest { string key = 1;}message GetResponse { bytes value = 1;}message PutRequest { string key = 1; bytes value = 2;}message Empty {}service KV { rpc Get(GetRequest) returns (GetResponse); rpc Put(PutRequest) returns (Empty);} 定义对外暴露的插件接口 新建pkg/shared/plugin.