项目背景
最近在做一个路由监控相关的项目,主要需求是采集交换机上发出的bgp数据。
传统的方式采用的是将服务器模伪装成交换机,和其他交换机建立bgp连接,将交换机对交换机发布的路由数据进行收集,这样做有一个问题,就是与每个交换机的连接都需要建立一个进程,当需要采集的交换机数量过多时,就会非常占据服务器的资源,并且进程的稳定性也无法保证,而重新与交换机建立bgp连接的过程又相当繁琐。并且,路由器通过bgp协议广播出来的的数据是经过计算后的路由表的最终数据,无法获取到原始数据。
因此,我们目前的路由采集系统正在围绕着下一代的路由监控协议进行迁移。BMP 协议,全称是BGP Monitoring Protocol (BMP) BGP Monitoring Protocol (BMP)。该协议的主要功能是为服务器提供一个监控交换机BGP session 的接口,交换机与服务器建立tcp连接,单向的向服务器发送路由数据。
BMP 协议在建立连接之后,交换机会立刻向服务器吐出当前设备缓存内的所有路由数据,当缓存内的数据发送完毕后,会发送后续更新的路由数据。按照估算,我们系统之后将接入几千甚至上万的网络设备。每一次网络设备接入后,都会向系统发送大量数据。但是目前BMP 协议还在不断完善,我们的解析代码也在更新,如果每次版本迭代后,都需要重启进程,让设备重新连接系统,发送缓存中的所有路由的话,那会对网络造成很大的压力,甚至有可能会影响到在线的业务。
因此,在系统实现的时候,需要将路由监控系统的网络连接管理与BMP协议解析模块拆分开来,其中网络连接管理部分需要始终保持稳定,不会因为解析不当而进程奔溃,进而导致当前服务器上连接的所有BMP session都断开重连。其次,由于 BMP 协议还有可能会变化,并且还需要适配各个厂家的设备,因此协议解析模块需要实现热更新,保证在不影响主进程的情况下达到更新协议解析代码的目的。
我采用了将协议解析模块插件化(Plugin)的方式来实现我的功能。
什么是插件化,插件化可以解决什么问题
首先我想概括一下我的需求点。
- 对BMP协议的解析不会影响到主进程的稳定性,比如当解析到越界的数组时,解析模块奔溃,主进程不会奔溃。
- 解析模块的代码可以热更新,仅需要修改相应模块的代码并且重新编译后,替换原有的可执行文件。
其实这么看来,我理想中的实现方式和c++项目里的动态链接库特别像,但是奈何golang语言目前并不支持动态链接库。
官方的 plugin 模块
在golang 1.18 版本中,官方推出了plugin模块,顾名思义,该模块可以实现将代码插件化,在某种程度上实现我所需要的功能。但是很遗憾,官方对于维护这个功能的热情似乎不是很高,截止至2022年2月,这个插件模块的功能还是有些简陋,主要包括以下几点缺陷。
- 不支持插件的关闭,只能open新的插件替代当前的。短期可能没什么问题,但是随着版本的更新,泄漏的内存会越来越多。
- 即使是编译新版本的插件,也需要保持代码的构建环境和依赖库版本和最初编译时完全一致。
- 目前仅支持一些操作系统,其中不包括windows。
因此,我最终并没有选择golang官方提供的plugin来开发我的项目,而是将目光投向了一些开源项目。
Go-plugin
经过一番探索,我发现了这个开源项目。 Go-plugin 是hashicorp公司基于rpc方式实现的插件化组件,该组件可以帮助程序员实现golang代码的热更新。通过预先定义好接口,程序员可以灵活的更新插件化的代码,即使程序正在运行,只需要替换掉编译后的可执行文件,就可以实现代码的热更新,仿佛开着飞机换引擎一般。
插件特性
- 该插件系统是通过go 接口实现的,你只需要定义和实现好你的接口代码,并且在进程中调用他们。go-plugin 会自动的帮程序员处理rpc的交互细节。
- 跨语言支持:因为插件可以通过grpc调用,因此插件的接口虽然是golang实现的,但是具体的实现可以交给其他的语言,只要grpc支持。
- 复杂参数和返回值支持。
- 双向通讯。主进程可以调用插件的函数, 插件可以通过函数回调的方式向主进程发送数据。
- 内建的日志系统。
- 协议版本。一个非常基础的协议版本可以用来区别新增的和以前的插件。当接口有了明显的改变后,可以用版本号来区别,当主进程和插件的协议不匹配时,会返回错误。
- 插件运行时,主进程更新。 当宿主进程需要更新时,插件可以在保持运行的状态下重新获取。
- 加密安全插件。可以使用预期的校验和验证插件,并且可以将 RPC 通信配置为使用 TLS。必须适当保护主机进程以保护此配置。
我这里只是搬运了一下官方的readme,更加详细的信息大家可以去看官方的介绍。
本篇文章我将重点介绍如何使用这个库。