go: 官方插件(plugin)初探

2024-01-17 09:30:34 浏览数 (2)

写一个最简单的插件

在实践中学习,动动手指

ch.go

代码语言:javascript复制
package main

import "common/greeterinterface"

type ChineseGreeter struct {
}

func (g *ChineseGreeter) Greet() string {
    return "世界你好"
}

// 这个就是可导出的函数,插件在lookup这个函数
func NewGreeter() greeterinterface.Greeter {
    return new(ChineseGreeter)
}

编译插件

代码语言:javascript复制
go build -buildmode=plugin -o ./ch.so ch.go

在主程序中载入并运行插件

代码语言:javascript复制
package plug

import (
    "plugin"
    "common/greeterinterface" // 主程序中,仍然引用了这个接口,它是对插件的桥梁
    "log"
)


func Load() (err error) {
    // 打开插件
    plug, err := plugin.Open("./ch.so")
    if err!=nil{
        log.Fatalf("load plugin failed:%v", err)
    }

    // 寻找对象
    greeterObj, err := plug.Lookup("NewGreeter")
    if err!=nil{
        log.Fatalf("look up failed :%v", err)
    }

    // 转换对象的格式
    creator := greeterObj.(func() greeterinterface.Greeter)
    
    greeter := creator() // 此时调用的是插件中的函数
    greeter.Greet() // 创建的结构和直接写代码new的无区别

    return nil
}

几点建议

太棒了,现在你已经获得了写插件的新技能。这里有几点实现插件的不成熟建议:

  1. 插件需要定义在main包。按通常的方式编写函数和结构体即可。
  2. 定义接口在第三方的module中,插件实现这个接口。比如下面的common/greeterinterface
  3. 在插件中定义构造函数,如下面的NewGreeter
  4. 在主程序中,引用插件中的构造函数即可创建对象。

有哪些坑

  1. go的插件只支持open,不支持close。这样会有内存泄露。
  2. go的插件,相同的module name只支持加载一次。
  3. 一定要使用go.mod进行项目管理,插件和主程序的所有相同的依赖版本要相同,建议用主程序的go.mod内容同步到插件的go.mod。否则在open的时候将直接报错。
  4. 测试发现,调用无业务的Greet函数,原生 go 比插件性能快5倍,但只有纳秒级的区别。原生go执行1000万次函数耗时3ms,插件耗时 16ms。 而执行带业务的函数,性能差距则并不明显

为什么要使用插件

  1. 隐藏业务的代码实现。有时候多部门合作时,需要隐藏业务的实现。
    1. 平台方提供平台主程序和接口协议,具体业务由不同的部门实现。类似Linux的版本是开源的,但ko的实现则不一定。
    2. 插件和主程序共同引用的第三方包版本必须完全相同,这里为协作带来了不小的麻烦
  2. 分离业务实现,避免频繁的编译主程序框架。显然,将业务以文件的形式分离提供了更大的灵活性,但这种灵活性在实践中有这几个问题:
    1. 插件的体积很大。因为go的特点,每个插件so占用的内存和磁盘在20M以上。
    2. 版本管理不方便。so的版本以文件方式会有管理成本。
    3. 主程序仍然需要重启加载插件。因为插件只能Load而不能Close,且不能重复加载,目前没有很好的动态加载/卸载插件的方法

总体来说,plugin 包还有相当大的提升空间,但这似乎并不是go团队的关注重点。

0 人点赞