這個項目需要用到動態連結程式庫技術, 主程式動態載入一些功能模組,這樣在擴充功能時,無須修改主程式,只需要新增功能模組動態調用就可以了。 研究了一下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 mainimport ("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 Greetertype 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 mainimport ("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.Loggerlogger := 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 RPCrpcClient, err := client.Client()if err != nil {log.Fatal(err)}// Request the pluginraw, 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{},}