likes
comments
collection
share

go插件的能与不能

作者站长头像
站长
· 阅读数 133

Go属于静态编译类型语言,但有时候我们希望在线上系统中能给业务方开放更多干预服务逻辑的能力,但又不想完全开放整个系统能力。这时候让我想到了 Go 是否可以使用插件机制动态加载代码Go代码,下面总结了插件使用方法以及以及能与不能:

插件开发流程

开发一个插件倒是很简单,使用 Go 官方提供的 plugin 接口函数即可,大致流程分为如下三个步骤:

go插件的能与不能

1.设计插件功能

插件功能本身无非就是需要暴露出来的接口函数或变量,大多场景下都是和主程序约定好的接口函数,和编写其他Go 代码没有任何区别,不过还是有几点需要注意:

  1. 插件代码必须要要以一个 package main 包,但插件下的main函数不会调用,写了也没用;
  2. 肯定要有约定的暴露变量或者函数;
  3. 编译build的工具链版本最好和主程序保持一致,否则会撞坑。

比如这里设计了一个 filter.so 插件,过滤 nginx 日志中带有 /api 的请求日志:

package main // main至少要有一个,整个查件代码中

import (
   "strings"
)

func Filter(ngxLine string) bool {
   return strings.Contains(ngxLine, "/api")
}

再次强调: 一定要有main包,不然编译不过!。

2.打包so文件

编写完逻辑后,我们需要把插件打包成一个 so 动态库文件,使用 -buildmode=plugin 参数:

Go 其实不光能构建动态库,静态库也能构建,具体可参考: go help buildmode

go build -buildmode=plugin filter.go

3. 运行插件

使用该插件,使用 plugin.Open() 函数接口,加载静态库,然后调用约定好的方法或变量即可:

p, err := plugin.Open("filter.so")
if err != nil {
   return err
}

// 通过查符号表,找到函数地址
filter, err := p.Lookup("Filter")
if err != nil {
   return err
}

// 具体调用执行
f := filter.(func(ngxLine string) bool)
if !f(line) {
   // 跳过  
}

关于第三步,有如下一些总结:

  1. plugin.Open 函数对于相同的文件路径加载仅会执行一次,所以不用担心性能问题;但可能这也是问题所在,比如你更新了你的插件代码,希望主程序动态更新加载 so文件实现热更新, 这是做不到的

  2. plugin.Open 内部其实是一个以及文件路径的HashMap,有兴趣的可以自己屡一屡,但有一个细节,有一把互斥锁(mutex.Lock),所以对于插件的加载还要要尽早,比如放到主程序 init 环节;

  3. 插件的生命周期是当主程序调用 Open() 函数开始的,之后和主程序一起消亡,不能主动释放;因此一旦一个插件被加载之后,除非重启主程序,是没有其它办法更新这个插件的! 因此需要评估你的业务场景是否适合

结论:

最终,我们评估目前 Go 的插件机制结论大致如下:

  1. 除非重启,插件不能主动释放,所以热更新(hot-reload);
  2. 插件Open()有互斥锁,使用时可能要评估性能;
  3. 以动态库so文件加载,插件多了,内存占用要小心;
  4. windows下没戏, 仅支持linux,MacOS;

不过,虽说插件有上面一些不足地方,但也绝非一无是处,比如我们使用Go程序处理kafka消息队列数据,中间有若干数据过滤聚合环节,我们想把这些数据处理能力平台化,让接入的业务方定制自己的处理逻辑,这时候非常好用。

转载自:https://juejin.cn/post/7072279279978938399
评论
请登录