Skip to content

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 {}
}

插件化检查清单 ​

[ ] 定义清晰的插件接口
[ ] 实现插件管理器
[ ] 处理插件加载错误
[ ] 考虑插件版本兼容性
[ ] 实现插件间通信机制
[ ] 添加插件健康检查
[ ] 支持插件热重载
[ ] 记录插件日志
[ ] 限制插件资源使用
[ ] 考虑安全隔离