golang插件化方案

1. 背景

业务线的活动,每一次新活动都做独立项目开发,有大量重复代码,并且浪费数据服务的连接资源;排序服务也许要经常添加业务代码,目前是停服务发布……这些场景为了开发维护效率、稳定性、安全性和性能都使用了Go语言。Go是静态编译语言,在具体的动态场景该如何实现应用级别的持续交付呢?

基于k8s,nginx网关,队列回溯消费等工具的实现也可以实现不同程度的持续交付,但是持续交付的要求越高,搭建平台和维护的成本也越高。

从应用开发本身出发,可以考虑插件化

插件使用场景特点

  1. 可以热更新式扩展应用程序的功能列表
  2. 应对多变的业务需求,方便功能上下线
  3. 对于任意的go应用,能进行增量架构、代码分发以及代码上下线

插件设计标准

  • 性能:调用插件要尽可能的快;对于任务插件,使用单独的工作空间(协程、线程、进程的池子化处理),大的、慢的、长期运行的插件,要少调用
  • 稳定性:插件依赖的发布平台要少发布,交互API的设计要做好抽象,上下文的环境变量非必须不添加,减少升级需求,甚至能支持多个实例互备热升级
  • 可靠性:如果有失效、崩溃的可能,必须有快速、简单、完整的恢复机制;业务插件的执行不能影响依赖的发布平台的守护进程或者线程的稳定
  • 安全性:应该通过代码签名之类的手段防篡改
  • 扩展性:支持插件热更新和上下线,下线需要健康检查,公共库插件至少能热加载
  • 复用性:业务插件不要太多一次性的上下线
  • 易用性:提供使用简单、功能正交的API,业务插件能够获取依赖的发布平台的上下文和调用公共库

2. Go的插件方式

动态链接库plugin,官方文档

语言本身支持,插件和主程序原生语法交互

  • 进程隔离:无,单进程
  • 主程序调用插件:一切预协定object(包括function、channel)
  • 插件感知主程序上下文:主程序预定义类型参数object(包括function、channel)
  • stream支持:单向,基于channel
  • 插件发现:主程序循环扫描插件目录并维护状态;通过第三方文件diff工具维护,例如git
  • 上线:能
  • 下线:不能
  • 更新:不能
  • 通信:进程内
  • 序列化:不需要
  • 性能:高

Go plugin判断两个插件是否相同是通过比较pluginpath实现的,如果没有指定pluginpath,则由内部的算法生成, 生成的格式为plugin/unnamed-“ + root.Package.Internal.BuildID 。这种情况下,如果两个插件的文件名不同,引用包不同,或者引用的cgo不同,则会生成不同的插件,同时加载不会有问题。但是如果两个插件的文件名相同,相关的引用包也相同,则可能生成相同的插件,即使插件内包含的方法和变量不同,实现也不同。判断插件相同,热加载不会成功,也就意味着老插件不支持覆盖更新。

最好在编译的指定pluginpath,同时方便版本跟踪。目前生产环境建议一些公共库无服务依赖的函数,例如算法库之类的。

1go build -ldflags "-pluginpath=plugin/hot-$(date +%s)" -buildmode=plugin -o so/Eng.so eng/greeter.go

通信+序列化

natefinch/pie,github仓库

  • 进程隔离:有,多进程,provider+comsumer
  • 主程序调用插件:provider模式调用插件进程中预协定method;consumer模式消费插件进程中的预协定参数object(包括function、除了channel)
  • 插件感知主程序上下文:provider模式消费主程序的预定义参数object(包括function、除了channel);consumer模式调用主程序中预定义method
  • stream支持:不支持
  • 插件发现:主程序循环扫描插件目录并维护状态;通过第三方文件diff工具维护,例如git
  • 上线:能
  • 下线:能
  • 更新:能
  • 通信:支持stdin/stdout、pipe、unix socket、tcp、http、jsonrpc
  • 序列化:gob,protobuf, json, xml
  • 性能:中/偏高

基于Go的net/rpc库,无法支持主程序和插件之间的streaming数据交互,有golang的官方包[issue1]和[issue2]直接建议。另外,每一个插件都要开一个进程,因此要注意通信序列化的性能消耗和进程管理,默认使用stdin/stdout建立连接,如下图,一个plugin和主程序之间有两条单向连接。

可以上成产环境,要做好资源管理。

hashicorp/go-plugin,github仓库

  • 进程隔离:有,多进程,server+client
  • 主程序调用插件:一切协议预协定object
  • 插件感知主程序上下文:一切协议预协定object
  • stream支持:单向和双向,基于http/2
  • 插件发现:主程序循环扫描插件目录并维护状态;通过第三方文件diff工具维护,例如git
  • 上线:能
  • 下线:能
  • 更新:能
  • 通信:支持grpc
  • 序列化:protobuf
  • 性能:中/偏高

基于Google的grpc库,按照微服务的流程定义proto文件,能通信就能互相调用。知名团队出品。可以上成产环境,要做好资源管理。

go-mangos/mangos,github仓库

  • 进程隔离:有,多进程,provider+comsumer
  • 主程序调用插件:一切预协定object
  • 插件感知主程序上下文:一切预协定object
  • stream支持:单向,基于mq
  • 插件发现:主程序循环扫描插件目录并维护状态;通过第三方文件diff工具维护,例如git
  • 上线:能
  • 下线:能
  • 更新:能
  • 通信:支持mq
  • 序列化:未知
  • 性能:中/偏高


基于消息队列协议通信,nanomsg和ZeroMQ一类的规范包含一组预定义的通信拓扑(称为“可扩展性协议”),涵盖许多不同的场景:Pair,PubSub,Bus,Survey,Pipeline和ReqRep。Mangos是该协议的golang实现,能够灵活方便支地持两个插件交流。

可以上成产环境,要走大量的基础建设开发。

嵌入式脚本语言

一般都是进程内内嵌第三方语言的解释器,需要考虑解释器的工作线程资源的重复利用。

  • 进程隔离:无,单进程,解释器有goroutine开销
  • 主程序调用插件:一切语言协定object
  • 插件感知主程序上下文:一切语言协定object
  • stream支持:看语言是否支持channel互通
  • 插件发现:主程序循环扫描插件目录并维护状态;通过第三方文件diff工具维护,例如git
  • 上线:能
  • 下线:能
  • 更新:能
  • 通信:无
  • 序列化:无
  • 性能:中

go-like脚本语言,agora和七牛qlang

agora和qlang都是go语法的动态脚本语言,都好几年没维护了,建议不要用在生产环境,其中Qlang还有用户提[issue]觉得不稳定。

其他脚本语言,js-otto、go-lua5.1、go-lua5.2

otta支持目前受欢迎的js语法,star比较多,协定了大部分go原生支持的类型,不包括channel和goroutine,没有提供解释器的工作空间池子化管理,需要开发者使用goroutine和解释器的interrupt接口自行实现,但是从issue和TODO来看,也不适合生产环境。

gopher-lua支持lua5.1语法,和go交互的object类型比较完备,协定了大部分go原生支持的类型,包括channel和goroutine,有提供解释器的工作空间池子化管理,可以上生产环境。

go-lua支持lua5.2语法,目前不建议上生产环境。

3. 思考

  1. 主程序需要怎样设计才能给业务插件预定义完美的上下文呢?例如线程池、redis连接池、mysql连接池、rocketmq、外部服务依赖等等
  2. 公共库插件和业务插件是否适合不同的插件方式?公共库插件方便为业务插件增加提供上下文吗?

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2018-08-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IMWeb前端团队

尾递归的后续探究

0 前言 去年大致也是这个事件,曾经探索过尾调用(PTC)相关的内容,并总结了一片文章——朋友你听说过尾递归吗。同时在文章的最后也留下了一个坑: 尾递归写法的...

21710
来自专栏木可大大

漫谈Web缓存架构

目前,Web应用的核心数据通常存放在数据库中,比如说用户信息、订单信息、交易信息等,同时,数据库和编程语言是无关的,通过SQL交互,Java、Php等语言写的程...

44510
来自专栏Django中文社区

概述

通常情况下,Django 的视图函数(View)是一个纯粹的 Python 函数,它接收一个 request(HTTP 请求),返回一个 response(HT...

2797
来自专栏有趣的Python

21- vue django restful framework 打造生鲜超市 -首页商品分类显示功能

Django2.0.2(Django-rest-framework)以及前端vue开发的前后端分离的商城网站 线上演示地址: http://vueshop.mt...

4109
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第二十天 Redis学习【悟空教程】

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.i686

1865
来自专栏桥路_大数据

ElasticSearch配置外网访问,开放9200端口

5225
来自专栏一个爱吃西瓜的程序员

Python爬虫学习--爬虫基本架构

一个简单的爬虫架构由爬虫调度端、URL管理器、网页下载器和网页解析器四部分构成。它们之间的关系如下图: ? ● 爬虫调度端:启动爬虫,停止爬虫,监视爬虫的运行情...

3956
来自专栏IT派

秒懂Python编程中的if __name__ == 'main' 的作用和原理

一天偶然发现知乎上有篇关于对python编程中的if __name__ == 'main'的理解陈述,看完之后,自己觉得不够简单明了,于是在其文章底部写了一句话...

921
来自专栏Crossin的编程教室

【Python 第64课】python shell

各位好久不见,我终于又更新了:D。今天抽空来讲点非常非常基础的东西,关于在哪里写 python。 如果你已经编写过自己的程序,相信对这些内容已经熟悉。但很多刚刚...

35110
来自专栏跟着阿笨一起玩NET

从XML文件乱码问题,探寻其背后的原理

加入xml文件以<?xml version="1.0" encoding="utf-8" ?> 格式的;如果对xml文件进行修改了,其中包含中文字符的内容,另存...

1312

扫码关注云+社区

领取腾讯云代金券