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

说说我对插件的理解

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

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

libssllibsslglibc

go-plugin

github.com/hashicorp/go-plugingrpcGRPC

UML图

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

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

讲解

proto

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

//protoc -I proto/ proto/print.proto --go_out=plugins=grpc:proto/ --go_out=. --go_opt=paths=source_relative
syntax = "proto3";
option go_package = "github.com/sxy/try-go-plugin/proto";

package proto;

message Empty {}

service HelloPlugin {
  // Sends a greeting
  rpc Hello (Request) returns (Response) {}
}

// 对应uml中的request
message Request{
  string name = 1;
}
// 对应uml的response
message Response{
  string result = 1;
}

定义IHelloService 接口

IHelloService
type IHelloService interface {
    Hello(name string) (string, error)
}
IHelloServiceHelloService
type PluginService struct{}

func (p PluginService) Hello(name string) (string, error) {
    return "hello " + name, nil
}

(重要) 定义GRPCPlugin

GRPCPlugingo-pluginGRPCPlugin
go-plugin
// GRPCServer 负责注册一个grpc server,本例中就是实现proto.HelloPluginServer的struct实例.
GRPCServer(*GRPCBroker, *grpc.Server) error

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

看代码就一目了然了.

// GRPCHelloPlugin implement plugin.GRPCPlugin
type GRPCHelloPlugin struct {
    plugin.Plugin
    Impl IHelloService
}

// 注册HelloPluginServer
func (p GRPCHelloPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc.Server) error {
    proto.RegisterHelloPluginServer(server, GPRCHelloPluginServerWrapper{impl: p.Impl})
    return nil
}

// Host去获取插件的实例时,就掉用这个方法,将HelloPluginClient 作为 GRPCHelloPluginClientWrapper 的成员并返回GRPCHelloPluginClientWrapper
// 同时GRPCHelloPluginClientWrapper 也实现了IHelloService
func (p GRPCHelloPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, conn *grpc.ClientConn) (interface{}, error) {
    return GRPCHelloPluginClientWrapper{client: proto.NewHelloPluginClient(conn)}, nil
}

type GPRCHelloPluginServerWrapper struct {
    impl IHelloService
    proto.UnimplementedHelloPluginServer
}

func (_this GPRCHelloPluginServerWrapper) Hello(ctx context.Context, request *proto.Request) (*proto.Response, error) {
    r, _ := _this.impl.Hello(request.Name)
    return &proto.Response{
        Result: r,
    }, nil
}

// GRPCHelloPluginClientWrapper 作为server 调用插件接口的包装器,
type GRPCHelloPluginClientWrapper struct {
    client proto.HelloPluginClient
}

func (_this GRPCHelloPluginClientWrapper) Hello(name string) (string, error) {
    in := proto.Request{Name: name}
    resp, err := _this.client.Hello(context.Background(), &in)
    if err != nil {
        return "", err
    } else {
        return resp.Result, nil
    }
}

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

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

Host 调用插件

func main() {
    log.SetOutput(os.Stdout)
    pluginClientConfig := &plugin.ClientConfig{
        HandshakeConfig:  shared.Handshake,
        // helloPlugin.exe 是我们编译插件得到的可执行文件
        Cmd:              exec.Command("./helloPlugin.exe"),
        // Host 只使用 GRPCHelloPlugin 的 GRPCClient 方法,无需使用任何GRPCHelloPlugin内部成员
        Plugins:          map[string]plugin.Plugin{"main": &shared.GRPCHelloPlugin{}},
        AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
    }

    client := plugin.NewClient(pluginClientConfig)
    pluginClientConfig.Reattach = client.ReattachConfig()
    protocol, err := client.Client()
    if err != nil {
        log.Fatalln(err)
    }
    // 实例化,此处实例化得到的其实就是 GRPCHelloPluginClientWrapper
    raw, err := protocol.Dispense("main")
    if err != nil {
        log.Fatalln(err)
    }
    // 类型断言为IHelloService接口, 也可以用反射调用函数
    service := raw.(shared.IHelloService)
    res, err := service.Hello("sxy")
    if err != nil {
        log.Fatalln(err)
    }
    log.Println(res)
}

最终效果

编译插件

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

运行server

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

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

完成代码实现