References
- hashicorp/go-plugin: Golang plugin system over RPC.
Go Plugin
Bi-Directional Plugin
双向的实现,UserDefinedPlugin 可以包含一个公共的 Interface 实现,而 Interface 的实现在 Main Process 端。当 Main Process 获取到 UserDefinedPlugin 后,初始化该公共 Interface,再使用 Plugin 时,公共接口功能就由 Main Process 来实现了。
Start Plugin Process
Main Process 需要创建一个 ClientConfig,用于关联 Plugin 的可执行文件,并关联感兴趣的 Plugin 实现。然后根据 ClientConfig 启动 Plugin Process。当 Plugin Process 启动后,会通过 Stdout 告知 Main Process,后续通信使用 NetRPC 还是 GRPC。
GRPC
Start Server
在 Plugin 自定义实现中启动,启动过程比较简单,使用 plugin.Serve 即可,自定义 Plugin 的实现通过 map[string]plugin.Plugin 注册。
func main() {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: shared.Handshake,
Plugins: map[string]plugin.Plugin{
"kv": &shared.KVGRPCPlugin{Impl: &KV{}},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}
注意要点
除 Windows 操作系统外,Plugin 沟通全部通过本地 Unix Socket 来进行
func serverListener() (net.Listener, error) {
if runtime.GOOS == "windows" {
return serverListener_tcp()
}
return serverListener_unix()
}
核心信息通过 Stdout 输出,由于 Main Process 是父进程,自然可以捕获子进程的 Stdout
fmt.Printf("%d|%d|%s|%s|%s|%s\n",
CoreProtocolVersion,
protoVersion,
listener.Addr().Network(),
listener.Addr().String(),
protoType,
serverCert)
Register Custom Service
plugin.Serve 关键代码部分,配置好内部使用的 Broker、Controller 服务后,将控制权转移给 GRPCPlugin,然后实现者可自行实现自己的代码逻辑。
GRPCPlugin 接口定义如下所示。GRPCServer 方法用于启动 GRPC 服务端功能,实现者可在此注册自定义的服务实现代码。GRPCClient 方法一般用于与 GRPCServer 中注册的服务对应的客户端功能实现。由于两个方法中均传入了 GRPCBroker,还可以做其他工作,后面会详细说明。
type GRPCPlugin interface {
// GRPCServer should register this plugin for serving with the
// given GRPCServer. Unlike Plugin.Server, this is only called once
// since gRPC plugins serve singletons.
GRPCServer(*GRPCBroker, *grpc.Server) error
// GRPCClient should return the interface implementation for the plugin
// you're serving via gRPC. The provided context will be canceled by
// go-plugin in the event of the plugin process exiting.
GRPCClient(context.Context, *GRPCBroker, *grpc.ClientConn) (interface{}, error)
}
Get RPC Client
在程序主框架中,一般通过 plugin.Client 创建一个新的 Client 对象。并通过 Client.Client() 方法获取与自定义 Plugin 中 RPC 服务对应的客户端(RPC、gRPC)。 在客户端中,实现了 Broker、Controller 服务的调用。
Dispense
Dispense 方法,最终触发了 GRPCPlugin 的 GRPCClient 方法,获取到自定义 Plugin 的客户端代码部分。后续可通过该方法获取的实现,做具体操作。
GRPC Broker
Proto
ConnInfo 消息定义如下
message ConnInfo {
uint32 service_id = 1;
string network = 2;
string address = 3;
}
GRPCBroker 服务定义如下
service GRPCBroker {
rpc StartStream(stream ConnInfo) returns (stream ConnInfo);
}
Landscape
plugin.GRPCServer 启动时,将 GRPCBrokerServer 注册到 grpc.Server 中,并调用 GRPCBroker.Run 方法启动 ConnInfo 收、发处理的协程。
plugin.GRPCClient 启动时,底层的 grpc.ClientConn 会发起到 grpc.Server 的连接,并调用 GRPCBroker.Run 方法启动 ConnInfo 收、发处理的协程。
Communication
RPC
Server Main Procedure
Mux Broker
yamux.Session 会开启三个 Stream,分别为主控 Control Stream,标准输出 Stdout Stream 及标准错误 Stderr Stream。
Control Stream 用于发送 RPC 请求、应答。
Dispense
MuxBroker 用于控制 yamux.Session 生成新的 Stream,新 Stream 共享底层 net.Conn 对象,也就是说,Client 与 Server 间只需要一个 Connection 就可以实现多个流。