golang 基于grpc的插件框架——go-plugin 使用入门

golang 基于grpc的插件框架——go-plugin 使用入门

说说我对插件的理解

大家都用过vscode,当我们想要在vscode中格式化json的时候,很简单,去插件市场安装一个json tools就好了;想要使用eclipse的键盘快捷方式,安装一个eclipse keymap 就可以. 由此可见,插件帮助我们扩展原有程序的功能,同时它与原有工程是解耦的,可以独立开发。 总结下:

  • 插件架构的功能
    • 为系统提供扩展能力
    • 不侵入系统现有功能
  • 插件的好处
    • Host与插件的代码解耦,独立开发
    • Host 只关注插件的接口,不关注实现细节
    • Host动态引入插件,因而可以自由定制所需的能力,避免部署包的体积过大
    • 插件可以独立升级

一些常见的插件架构设计思路

  1. 共享库方式。
    • 优点:动态编译,发布件小
    • 缺点: a. 共享库,决定了发布件必然是动态编译构建的,跨平台能力会比较弱。比如你要开发个https下载功能,要依赖libssl吧,而libssl又依赖glibc,于是这个依赖链就产生了各种版本上的强依赖。 b. 不安全。共享库方式调用插件代码,相当于把共享库的代码附加到当前进程,其函数可以直接访问当前主系统进程的内存空间,不仅不安全,而且如果插件代码质量低,可能导致主系统直接崩溃 c. 共享库的日志输出到主系统很麻烦 d. 如果用c语言开发还好,要是换个编程语言,还要涉及到数据类型的转换,恶心。。。
  2. 基于轻量通信协议的方式
    • 优点:能解决以上所有问题

go-plugin

接下来我要介绍下github.com/hashicorp/go-plugin,go-plugin使用grpc协议来完成插件与主平台的接口调用. (不熟悉GRPC的话,后文阅读起来可能比较难懂)

UML图

avatar

请对照UML图浏览后续内容,更有助于理解

讲解

proto

我们基于一个非常简单的protobuf来实现插件与HOST的通信

1//protoc -I proto/ proto/print.proto --go_out=plugins=grpc:proto/ --go_out=. --go_opt=paths=source_relative 2syntax = "proto3"; 3option go_package = "github.com/sxy/try-go-plugin/proto"; 4 5package proto; 6 7message Empty {} 8 9service HelloPlugin { 10 // Sends a greeting 11 rpc Hello (Request) returns (Response) {} 12} 13 14// 对应uml中的request 15message Request{ 16 string name = 1; 17} 18// 对应uml的response 19message Response{ 20 string result = 1; 21}

定义IHelloService 接口

Host将插件实例化后的对象识别为接口——IHelloService接口,我们简单定义一个IHelloService

1type IHelloService interface { 2 Hello(name string) (string, error) 3}

插件中会完成对IHelloService的实现——HelloService

1type PluginService struct{} 2 3func (p PluginService) Hello(name string) (string, error) { 4 return "hello " + name, nil 5}

(重要) 定义GRPCPlugin

插件要里通过go-plugin框架来注册一个 GRPCPlugin 实例, Host 会利用go-plugin的框架,来导入GRPCPlugin实例

GRPCPlugin接口时go-plugin提供的grpc 插件标准接口,只有两个成员函数

1// GRPCServer 负责注册一个grpc server,本例中就是实现proto.HelloPluginServer的struct实例. 2GRPCServer(*GRPCBroker, *grpc.Server) error 3 4// GRPCClient 要返回一个实现了IHelloService接口的struct实例, 同时利用本方法传入的grpcConnection实例来调用proto.NewHelloPluginClient生成grpc通信所需的客户端实例, 这样它就能作为IHelloService接口方法与HelloPlugin接口(GRPC生成代码)的适配层. 5GRPCClient(context.Context, *GRPCBroker, *grpc.ClientConn) (interface{}, error)

看代码就一目了然了.

1// GRPCHelloPlugin implement plugin.GRPCPlugin 2type GRPCHelloPlugin struct { 3 plugin.Plugin 4 Impl IHelloService 5} 6 7// 注册HelloPluginServer 8func (p GRPCHelloPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) error { 9 proto.RegisterHelloPluginServer(server, GPRCHelloPluginServerWrapper{impl: p.Impl}) 10 return nil 11} 12 13// Host去获取插件的实例时,就掉用这个方法,将HelloPluginClient 作为 GRPCHelloPluginClientWrapper 的成员并返回GRPCHelloPluginClientWrapper 14// 同时GRPCHelloPluginClientWrapper 也实现了IHelloService 15func (p GRPCHelloPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, conn *grpc.ClientConn) (interface{}, error) { 16 return GRPCHelloPluginClientWrapper{client: proto.NewHelloPluginClient(conn)}, nil 17} 18 19type GPRCHelloPluginServerWrapper struct { 20 impl IHelloService 21 proto.UnimplementedHelloPluginServer 22} 23 24func (_this GPRCHelloPluginServerWrapper) Hello(ctx context.Context, request *proto.Request) (*proto.Response, error) { 25 r, _ := _this.impl.Hello(request.Name) 26 return &proto.Response{ 27 Result: r, 28 }, nil 29} 30 31// GRPCHelloPluginClientWrapper 作为server 调用插件接口的包装器, 32type GRPCHelloPluginClientWrapper struct { 33 client proto.HelloPluginClient 34} 35 36func (_this GRPCHelloPluginClientWrapper) Hello(name string) (string, error) { 37 in := proto.Request{Name: name} 38 resp, err := _this.client.Hello(context.Background(), &in) 39 if err != nil { 40 return "", err 41 } else { 42 return resp.Result, nil 43 } 44}

插件 main 启动grpc并注册自己的服务

1func main() { 2 plugin.Serve(&plugin.ServeConfig{ 3 HandshakeConfig: shared.Handshake, 4 Plugins: map[string]plugin.Plugin{ 5 "PrintPlugin": &shared.GRPCHelloPlugin{Impl: &PluginService{}}, 6 }, 7 // A non-nil value here enables gRPC serving for this plugin... 8 GRPCServer: plugin.DefaultGRPCServer, 9 }) 10}

Host 调用插件

1func main() { 2 log.SetOutput(os.Stdout) 3 pluginClientConfig := &plugin.ClientConfig{ 4 HandshakeConfig: shared.Handshake, 5 // helloPlugin.exe 是我们编译插件得到的可执行文件 6 Cmd: exec.Command("./helloPlugin.exe"), 7 // Host 只使用 GRPCHelloPlugin 的 GRPCClient 方法,无需使用任何GRPCHelloPlugin内部成员 8 Plugins: map[string]plugin.Plugin{"main": &shared.GRPCHelloPlugin{}}, 9 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 10 } 11 12 client := plugin.NewClient(pluginClientConfig) 13 pluginClientConfig.Reattach = client.ReattachConfig() 14 protocol, err := client.Client() 15 if err != nil { 16 log.Fatalln(err) 17 } 18 // 实例化,此处实例化得到的其实就是 GRPCHelloPluginClientWrapper 19 raw, err := protocol.Dispense("main") 20 if err != nil { 21 log.Fatalln(err) 22 } 23 // 类型断言为IHelloService接口, 也可以用反射调用函数 24 service := raw.(shared.IHelloService) 25 res, err := service.Hello("sxy") 26 if err != nil { 27 log.Fatalln(err) 28 } 29 log.Println(res) 30}

最终效果

编译插件

go build -o helloPlugin.exe plugin/plugin.go

运行server

go build -o server.exe server/server.go

avatar

完成代码实现

try-go-plugin

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )