背景

项目中由于需要底层业务需要动态调整,调研后组里决定使用plugin方法来解决动态加载业务的问题。 在使用过程中发现,当公共库更新后,只要平台程序或者插件并不能同时更新,程序在动态加载插件时候会出现

plugin.Open: plugin was built with a different version of package`

导致插件加载不成功。

环境: Go Version:1.9.2 GOOS=“linux” GOARCH=“amd64” GCCGO=“gccgo” CC=“gcc” CGO_ENABLED=“1”

正常运行的火焰图: torch 900s

平台实例代码:

p, err := plugin.Open(pluginPath)
if err != nil {
    log.Logger.Warnf("[插件更新] 打开插件[%s]失败,详情:[%v]", pluginPath, err)
    return
}

f, err := p.Lookup("Version")
if err != nil {
    log.Logger.Warnf("[插件更新] 获取插件[%s]版本失败,详情:[%v]", pluginPath, err)
    return
}

version := f.(func() string)()
log.Logger.Infof("[插件更新] 载入插件[%s]成功,BU名称:[%s],版本:[%s]", pluginPath, refer, version)

业务插件实例代码:

package main

const(
    VERSION = "XX_XX_XX"
)

//版本号输出
func Version() string {
    return VERSION
}

//TODO

原因

通过查看plugin包源代码,可以查到问题出在
$GOROOT/src/plugin/plugin_dlopen.go:116

    pluginpath, syms, mismatchpkg := lastmoduleinit()
    if mismatchpkg != "" {
        pluginsMu.Unlock()
        return nil, errors.New("plugin.Open: plugin was built with a different version of package " + mismatchpkg)
    }

核心的问题是在于mismatchpkg即存在有不匹配的依赖链接包。再深入到lastmoduleinit()函数中查看不匹配的原因是什么
$GOROOT/src/runtime/plugin.go:45

    for _, pkghash := range md.pkghashes {
        if pkghash.linktimehash != *pkghash.runtimehash {
            return "", nil, pkghash.modulename
        }
    }

真正的原因出在了这里,在载入插件时候,Go内部会获取插件的ABI信息,并保存其依赖项链接信息运行环境信息的哈希。因此当运行程序载入插件时候,会获取相关信息并与程序内部依赖项信息进行比对。

而我们实际情况是,在插件平台程序共同调用某一个包的时候,这个包改变了。但是插件和平台程序并未同时编译更新。因此造成了核验失败。

相关讨论: https://github.com/golang/go/issues/21373

解决方法

目前调研后测试通过的解决方法有以下两种:

  • 1.平台程序与插件同时更新。 > 对于集成项目,插件需要调用到平台package的情况,在平台程序更新的时候,也同时把有调用到新修改package的插件也给更新,并同时部署。

  • 2.独立共用package > 对于业务插件可以独立出来而且与平台耦合度不强的情况,可以将公用的package独立出来一份单独提供给插件程序调用,独立维护。平台的package更新将不再影响插件的依赖项。

文章目录