Golang | 插件化方案

2,571 阅读4分钟

1、什么是插件(也叫动态库)

在写C++程序时,时常需要将一个class写成DLL(动态链接库),供客户端程序调用。这样的DLL可以导出整个class,也可以导出这个class的某个方法。

通过DLL调用和把代码写在程序里调用的区别:看这个函数是否提供给别的程序调用;

别的程序肯定没法调用这个程序的某个函数,总不能把代码拷给他把,且不说可不可以拷,就算可以也麻烦阿,直接写成DLL让他自己调用去;

插件就类似dll;

通过使用插件在运行时扩展程序的功能, 而无需重新编译程序;

启动启程之后不用停止就能添加新的功能(函数);

优点:

  • 动态加载, 也称热加载, 每次升级时不用重新编译整个工程,重新部署服务, 而是添加插件时进行动态更新。这对于很多比较重型的服务来说非常重要。

缺点:

  • 带来一定的安全风险, 如果一些非法模块被注入如何防范;
  • 给系统带来一定的不稳定的因素, 如果模块有问题, 没有经过良好的测试, 容易导致服务崩溃

2、Go的插件系统:Plugin

Go插件是使用-buildmode = plugin标记编译的一个包, 用于生成一个共享对象(.so)库文件。

Go包中的导出的函数和变量被公开为ELF符号,可以使用plugin包在运行时查找并绑定ELF符号。这样就可以查询并调用插件里的函数;

3、插件设计原则

  • 插件独立
  • 使用接口类型作为边界
  • Unix模块化原则
  • 版本控制

4、go使用插件

  • 用plugin.Open()打开插件文件

  • 用plguin.Lookup(“Export-Variable-Name”)查找导出的符号”Car”或者”Phone”(就是函数名)

    请注意,符号名称与插件模块中定义的变量名称相匹配

  • 类型断言,然后就可以进行调用了

go build -buildmode=plugin file.go,编译生成插件

==go中的插件不是进程隔离的,是单进程的;==

golang的插件很不完善,只能加载不能卸载。如何需要卸载需要自己实现一套重载机制。

5、使用plugin注意

Go plugin判断两个插件是否相同是通过比较pluginpath实现的,如果没有指定pluginpath,则由内部的算法生成, 生成的格式为plugin/unnamed-“ + root.Package.Internal.BuildID 。

在开始编码之前,要先解决几个问题:
1、同一个so文件只会被打开一次
2、每个so有一个pluginpath用来标识是否重复,如果两个so文件不一样,但pluginpath一样还是会报错。会被识别成是同一个文件,同时加载这两个插件时,会报错;
3、不同so文件定义的结构体不能使用类型断言进行转换

对于上面的问题,有如下解决方案:
编译的时候新增--ldflags="-pluginpath=xxx"参数,自己指定pluginpath参数,避免重复。

6、关于插件内存

加载.so文件的时候,就会为全局变量申请内存空间,同时执行init()初始化函数;

同一进程空间内的线程:
堆:是大家共有的空间
栈:是个线程独有的

同一进程空间,函数可以直接相互调用;

思考:我在插件中调用项目的其他部分,如models,编译插件的时候不会报错吗?我插件都还没加载?

首先插件又叫动态库,和静态库没什么区别。静态库就是程序编译完了之后,再来修改静态库就程序不起作用,要重新编译才行。

而动态库就不同了,在程序编译运行之后,动态库可以照样更改,改完之后程序重新加载一遍就完事了;

至于插件中调用了models,毛问题都没有,插件编译的时候都没调用到models,而且插件只是编译而已,就算调用有问题,那也是等插件被加载之后的事情了;