这个项目需要用到动态链接库技术, 主程序动态加载一些功能模块,这样在扩充功能时,无须修改主程序,只需要新增功能模块动态调用就可以了。 研究了一下golang官方支持的plugin功能,发现有几点不足。
1.官方plugin功能本质上是用cgo实现的,编译一个so文件,然后再调用
2. 只支持linux, 不支持windows
3. plugin模块panic时, 主程序也会panic, 无法做到隔离。
基于上述原因,我开始另外寻找合适的第三方支持。后来发现这样一个开源库,https://github.com/hashicorp/go-plugin , 感觉符合我的需求。它基于net/rpc ,grpc实现,主程序和plugin程序是两个qtj独立进程,可以通过主程序调用plugin进程启动,也可以附加进程的方式。通过本地网络通讯,达到类似动态链接库调用的效果。
官方示例如下:
plugin:
package main import ( "os" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin/examples/basic/commons" ) // Here is a real implementation of Greeter type GreeterHello struct { logger hclog.Logger } func (g *GreeterHello) Greet() string { g.logger.Debug("message from GreeterHello.Greet") return "Hello!" } // handshakeConfigs are used to just do a basic handshake between // a plugin and host. If the handshake fails, a user friendly error is shown. // This prevents users from executing bad plugins or executing a plugin // directory. It is a UX feature, not a security feature. var handshakeConfig = plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "BASIC_PLUGIN", MagicCookieValue: "hello", } func main() { logger := hclog.New(&hclog.LoggerOptions{ Level: hclog.Trace, Output: os.Stderr, JSONFormat: true, }) greeter := &GreeterHello{ logger: logger, } // pluginMap is the map of plugins we can dispense. var pluginMap = map[string]plugin.Plugin{ "greeter": &example.GreeterPlugin{Impl: greeter}, } logger.Debug("message from plugin", "foo", "bar") plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: handshakeConfig, Plugins: pluginMap, }) }
主程序 (调用方):
package main import ( "fmt" "log" "os" "os/exec" hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin/examples/basic/commons" ) func main() { // Create an hclog.Logger logger := hclog.New(&hclog.LoggerOptions{ Name: "plugin", Output: os.Stdout, Level: hclog.Debug, }) // We're a host! Start by launching the plugin process. client := plugin.NewClient(&plugin.ClientConfig{ HandshakeConfig: handshakeConfig, Plugins: pluginMap, Cmd: exec.Command("./plugin/greeter"), Logger: logger, }) defer client.Kill() // Connect via RPC rpcClient, err := client.Client() if err != nil { log.Fatal(err) } // Request the plugin raw, err := rpcClient.Dispense("greeter") if err != nil { log.Fatal(err) } // We should have a Greeter now! This feels like a normal interface // implementation but is in fact over an RPC connection. greeter := raw.(example.Greeter) fmt.Println(greeter.Greet()) } // handshakeConfigs are used to just do a basic handshake between // a plugin and host. If the handshake fails, a user friendly error is shown. // This prevents users from executing bad plugins or executing a plugin // directory. It is a UX feature, not a security feature. var handshakeConfig = plugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "BASIC_PLUGIN", MagicCookieValue: "hello", } // pluginMap is the map of plugins we can dispense. var pluginMap = map[string]plugin.Plugin{ "greeter": &example.GreeterPlugin{}, }
有疑问加站长微信联系(非本文作者)