7.7 插件化架构
plugin 包使用
创建插件
go
// plugin/math/math.go
package main
import "fmt"
// Export 的函数必须是导出的
func Add(a, b int) int {
return a + b
}
func Sub(a, b int) int {
return a - b
}
// Export 变量
var Version = "1.0.0"
// Export 类型
type Calculator struct {
Name string
}
func (c *Calculator) Multiply(a, b int) int {
return a * b
}
// 初始化函数
func init() {
fmt.Println("Math plugin loaded")
}编译插件
bash
# 编译插件 (必须是 main 包)
go build -buildmode=plugin -o math.so plugin/math/math.go
# 注意:
# 1. Go 版本必须一致
# 2. 依赖必须完全相同
# 3. 仅支持 Linux/macOS加载插件
go
package main
import (
"fmt"
"plugin"
)
func main() {
// 打开插件
p, err := plugin.Open("math.so")
if err != nil {
panic(err)
}
// 查找函数
addSym, err := p.Lookup("Add")
if err != nil {
panic(err)
}
add := addSym.(func(int, int) int)
result := add(2, 3)
fmt.Println("Add:", result) // 5
// 查找变量
versionSym, err := p.Lookup("Version")
if err != nil {
panic(err)
}
version := *versionSym.(*string)
fmt.Println("Version:", version)
// 查找类型
calcSym, err := p.Lookup("Calculator")
if err != nil {
panic(err)
}
CalcType := calcSym.(*Calculator)
calc := &CalcType{Name: "MyCalc"}
fmt.Println("Calculator:", calc.Name)
}插件接口定义
定义插件规范
go
// plugin/interface.go
package plugin
// Plugin 插件接口
type Plugin interface {
// Name 返回插件名称
Name() string
// Version 返回插件版本
Version() string
// Init 初始化插件
Init(config map[string]interface{}) error
// Start 启动插件
Start() error
// Stop 停止插件
Stop() error
// Health 健康检查
Health() bool
}
// PluginFactory 插件工厂函数
type PluginFactory func() Plugin实现插件
go
// plugins/logger/plugin.go
package main
import (
"log"
"os"
"github.com/example/app/plugin"
)
// 确保实现接口
var _ plugin.Plugin = (*LoggerPlugin)(nil)
type LoggerPlugin struct {
config map[string]interface{}
logger *log.Logger
}
func (p *LoggerPlugin) Name() string {
return "logger"
}
func (p *LoggerPlugin) Version() string {
return "1.0.0"
}
func (p *LoggerPlugin) Init(config map[string]interface{}) error {
p.config = config
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return err
}
p.logger = log.New(file, "", log.LstdFlags)
return nil
}
func (p *LoggerPlugin) Start() error {
p.logger.Println("Logger plugin started")
return nil
}
func (p *LoggerPlugin) Stop() error {
p.logger.Println("Logger plugin stopped")
return nil
}
func (p *LoggerPlugin) Health() bool {
return p.logger != nil
}
// 导出工厂函数
var Plugin = func() plugin.Plugin {
return &LoggerPlugin{}
}插件管理器
实现插件系统
go
package main
import (
"errors"
"fmt"
"plugin"
"sync"
)
// PluginInfo 插件信息
type PluginInfo struct {
Name string
Version string
Path string
}
// Manager 插件管理器
type Manager struct {
mu sync.RWMutex
plugins map[string]pluginHandle
registry map[string]string // 名称 -> 路径
}
type pluginHandle struct {
info PluginInfo
raw *plugin.Plugin
impl interface{}
}
func NewManager() *Manager {
return &Manager{
plugins: make(map[string]pluginHandle),
registry: make(map[string]string),
}
}
// Register 注册插件路径
func (m *Manager) Register(name, path string) {
m.mu.Lock()
defer m.mu.Unlock()
m.registry[name] = path
}
// Load 加载插件
func (m *Manager) Load(name string) error {
m.mu.Lock()
defer m.mu.Unlock()
path, ok := m.registry[name]
if !ok {
return errors.New("plugin not registered")
}
if _, loaded := m.plugins[name]; loaded {
return errors.New("plugin already loaded")
}
// 打开插件
p, err := plugin.Open(path)
if err != nil {
return fmt.Errorf("failed to open plugin: %w", err)
}
// 查找 Plugin 符号
sym, err := p.Lookup("Plugin")
if err != nil {
return fmt.Errorf("plugin missing 'Plugin' symbol: %w", err)
}
handle := pluginHandle{
raw: p,
impl: sym,
info: PluginInfo{
Name: name,
Path: path,
},
}
m.plugins[name] = handle
return nil
}
// Get 获取插件
func (m *Manager) Get(name string) (interface{}, error) {
m.mu.RLock()
defer m.mu.RUnlock()
handle, ok := m.plugins[name]
if !ok {
return nil, errors.New("plugin not loaded")
}
return handle.impl, nil
}
// List 列出已加载的插件
func (m *Manager) List() []PluginInfo {
m.mu.RLock()
defer m.mu.RUnlock()
infos := make([]PluginInfo, 0, len(m.plugins))
for _, h := range m.plugins {
infos = append(infos, h.info)
}
return infos
}
// Unload 卸载插件
func (m *Manager) Unload(name string) error {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.plugins[name]; !ok {
return errors.New("plugin not loaded")
}
// Go plugin 不支持卸载,只能标记为已删除
delete(m.plugins, name)
return nil
}
// Use 使用插件
func (m *Manager) Use(name string, fn func(interface{}) error) error {
impl, err := m.Get(name)
if err != nil {
return err
}
return fn(impl)
}热加载架构设计
文件监听与热重载
go
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"sync"
"time"
"github.com/fsnotify/fsnotify"
)
// HotLoader 热加载器
type HotLoader struct {
mu sync.RWMutex
watcher *fsnotify.Watcher
plugins map[string]*pluginInfo
reloadCh chan string
stopCh chan struct{}
}
type pluginInfo struct {
path string
modTime time.Time
loadedAt time.Time
}
func NewHotLoader(dir string) (*HotLoader, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
hl := &HotLoader{
watcher: watcher,
plugins: make(map[string]*pluginInfo),
reloadCh: make(chan string, 10),
stopCh: make(chan struct{}),
}
// 扫描目录
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".so" {
hl.plugins[path] = &pluginInfo{
path: path,
modTime: info.ModTime(),
}
}
return nil
})
// 添加监听
watcher.Add(dir)
// 启动监听协程
go hl.watch()
go hl.reload()
return hl, nil
}
func (hl *HotLoader) watch() {
for {
select {
case event, ok := <-hl.watcher.Events:
if !ok {
return
}
if filepath.Ext(event.Name) == ".so" {
// 文件修改,触发重载
hl.reloadCh <- event.Name
}
case err, ok := <-hl.watcher.Errors:
if !ok {
return
}
log.Println("watcher error:", err)
case <-hl.stopCh:
return
}
}
}
func (hl *HotLoader) reload() {
for {
select {
case path := <-hl.reloadCh:
hl.doReload(path)
case <-hl.stopCh:
return
}
}
}
func (hl *HotLoader) doReload(path string) {
hl.mu.Lock()
defer hl.mu.Unlock()
info, err := os.Stat(path)
if err != nil {
log.Printf("failed to stat %s: %v", path, err)
return
}
// 检查是否真的变化了
if pi, ok := hl.plugins[path]; ok {
if !info.ModTime().After(pi.modTime) {
return
}
}
log.Printf("reloading plugin: %s", path)
// 实际重载逻辑
// 1. 卸载旧插件 (Go 不支持,需要设计 workaround)
// 2. 加载新插件
// 3. 迁移状态
hl.plugins[path] = &pluginInfo{
path: path,
modTime: info.ModTime(),
loadedAt: time.Now(),
}
log.Printf("reloaded plugin: %s", path)
}
func (hl *HotLoader) Stop() {
close(hl.stopCh)
hl.watcher.Close()
}模块化系统设计
基于依赖注入的模块化
go
package main
import (
"fmt"
)
// Module 模块接口
type Module interface {
Name() string
Init() error
Start() error
Stop() error
}
// Container 依赖注入容器
type Container struct {
modules map[string]Module
deps map[string]interface{}
}
func NewContainer() *Container {
return &Container{
modules: make(map[string]Module),
deps: make(map[string]interface{}),
}
}
func (c *Container) Register(name string, module Module) {
c.modules[name] = module
}
func (c *Container) Provide(key string, val interface{}) {
c.deps[key] = val
}
func (c *Container) Get(key string) interface{} {
return c.deps[key]
}
func (c *Container) Start() error {
for name, module := range c.modules {
fmt.Printf("Starting module: %s\n", name)
if err := module.Init(); err != nil {
return fmt.Errorf("init %s: %w", name, err)
}
if err := module.Start(); err != nil {
return fmt.Errorf("start %s: %w", name, err)
}
}
return nil
}
func (c *Container) Stop() error {
for name, module := range c.modules {
fmt.Printf("Stopping module: %s\n", name)
if err := module.Stop(); err != nil {
return fmt.Errorf("stop %s: %w", name, err)
}
}
return nil
}
// 示例模块
type LoggerModule struct {
container *Container
}
func (m *LoggerModule) Name() string { return "logger" }
func (m *LoggerModule) Init() error { return nil }
func (m *LoggerModule) Start() error { return nil }
func (m *LoggerModule) Stop() error { return nil }
type DatabaseModule struct {
container *Container
}
func (m *DatabaseModule) Name() string { return "database" }
func (m *DatabaseModule) Init() error {
// 从容器获取依赖
_ = m.container.Get("logger")
return nil
}
func (m *DatabaseModule) Start() error { return nil }
func (m *DatabaseModule) Stop() error { return nil }
// 使用
func main() {
container := NewContainer()
// 注册模块
container.Register("logger", &LoggerModule{container: container})
container.Register("database", &DatabaseModule{container: container})
// 启动
container.Start()
defer container.Stop()
}WASM 基础
Go 编译为 WASM
bash
# 编译 Go 为 WASM
GOOS=js GOARCH=wasm go build -o main.wasm main.go
# 复制 wasm_exec.js (Go 运行时)
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .HTML 加载 WASM
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go WASM</title>
</head>
<body>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(
fetch("main.wasm"),
go.importObject
).then((result) => {
go.run(result.instance);
});
</script>
</body>
</html>Go WASM 代码
go
// +build js,wasm
package main
import (
"fmt"
"syscall/js"
)
// 导出给 JavaScript 调用的函数
func add(this js.Value, args []js.Value) interface{} {
a := args[0].Int()
b := args[1].Int()
return a + b
}
// 异步调用
func asyncFunc(this js.Value, args []js.Value) interface{} {
cb := args[0]
go func() {
// 模拟异步操作
result := "Hello from Go!"
cb.Invoke(result)
}()
return nil
}
func main() {
fmt.Println("Go WASM loaded")
// 导出函数
js.Global().Set("add", js.FuncOf(add))
js.Global().Set("asyncFunc", js.FuncOf(asyncFunc))
// 保持运行
select {}
}插件化检查清单
[ ] 定义清晰的插件接口
[ ] 实现插件管理器
[ ] 处理插件加载错误
[ ] 考虑插件版本兼容性
[ ] 实现插件间通信机制
[ ] 添加插件健康检查
[ ] 支持插件热重载
[ ] 记录插件日志
[ ] 限制插件资源使用
[ ] 考虑安全隔离