這個項目需要用到動態鏈接庫技術, 主程序動態加載一些功能模塊,這樣在擴充功能時,無須修改主程序,只需要新增功能模塊動態調用就可以了。 研究了一下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{},
}
