一种基于OSGi和Docker的SaaS平台热插拔系统设计方案-

软件即服务(SaaS)是软件服务业的一个重要发展方向,它使得软件业转向一个运营和服务的模式。大部分的SaaS研究集中于独立软件开发商多租户模型上,本文基于另一种业务模型:单核心软件开发商-多插件软件开发商多租户(Kernel-Plugins)模型。核心软件开发商提供核心的软件服务(Kernel)。插件软件开发商可以基于核心软件开发商提供的API,开发额外的插件服务(Plugin)。私有云平台运营者可以将云服务的核心和若干个插件进行部署,并提供给多个租户进行使用。

本文针对Kernel-Plugins模型中插件的在线集成和在线演化问题进行深入研究,并提供了一套基于OSGI技术、容器虚拟化技术、前端插件化技术的插件全生命周期管理解决方案。此方案实现了插件化的软件生命周期管理平台onboard。私有云服务提供商可以在不中断服务的情况下完成代码配置管理、迭代、持续集成等软件生命周期管理插件的安装、卸载、升级等操作。在私有云服务提供商改变服务内容时,租户的使用不受影响。证明了此方案是一种可行的SaaS平台插件在线集成、演化方案。

KERNEL-PLUGINS业务模型

SaaS生态系统

SaaS发展至今已有近十年的历史,尤其是近几年来,随着互联网接入和使用的大规模普及,软件服务提供商和使用者的供需市场日趋繁荣,SaaS取得了飞速发展,其服务品类和用户数量得以剧增。不断变革的软件产业正经历着由传统软件向“软件即服务”的裂变。

随着市场的不断扩展,产业由早期的SaaS服务商集软件设计开发、服务运营、市场营销、系统集成等角色于一身,到现在逐步开始的分工细化合作,SaaS产业的生态系统正逐渐形成。

在SaaS生态中,主要有以下几个角色:

  1. 基础设施供应商
  2. 服务组件供应商
  3. SaaS软件供应商
  4. SaaS运营商
  5. SaaS客户

其中,SaaS软件提供商是整个生态系统的关键角色。它掌握客户对SaaS软件的需求,并将之付诸实践。提供给SaaS运营商,并服务广大的SaaS客户。

SaaS运营商需要向基础设施供应商购买所需的计算及存储资源用来支撑SaaS软件的正常运行。

通常服务组件提供商也是一个SaaS服务提供商。SaaS软件供应商可以通过购买或授权的方式集成服务组件提供商的服务,并将服务组件组合构建新的服务。

从逻辑角度讲,参与SaaS生态系统的不同角色是相互独立存在的,但在实际环境中,很多企业扮演了多重角色。比如很多的SaaS软件供应商同时也在扮演SaaS运营商和服务组件提供商的角色。

模型的应用场景

之前我们介绍了SaaS生态系统中各个角色的作用。假定市场上有一个SaaS服务,它的业务组成为:

  1. SaaS软件供应商1名
  2. 服务组件供应商若干名
  3. SaaS运营商若干名
  4. 诸多SaaS客户

SaaS软件供应商提供某服务的核心功能,服务组件供应商基于该服务的核心功能与其他服务整合提供扩展组件。SaaS运营商根据自身资质和客户需求运营核心组件和若干扩展组件。SaaS客户可以根据自身需求的不同选择不同的SaaS运营商。

SaaS运营商的客户群体的需求是不断演变的,在一个SaaS服务的运营过程中,不可避免的要引入之前并未部署的服务组件来满足更多客户的需求。另一方面,SaaS运营商要将不经常使用的组件卸载以减少运营成本。然而,SaaS的运营是一个受SLA(Service Level Agreement)约束的,运营商需要在一定程度上保证服务的正常运行。所以就带来了一个新的需求。

SaaS运营商需要在不中断正常服务的情况下,安装、卸载、升级若干服务组件供应商提供的服务组件。

为了满足SaaS运营商的这个需求,我们提出了一套针对Kernel-Plugins模型的插件在线集成和在线演化方案。实践证明,这种方案能够有效的帮助SaaS运营商运行时改变服务的非核心组件。

Kernel

模型中的Kernel是由SaaS软件提供商提供的一套SaaS核心服务。它通常包含以下几个部分:

  1. 核心业务组件
  2. 插件注册服务
  3. 消息服务
  4. Etc.

Kernel的核心业务组件会完成SaaS的核心服务,这部分组件的接口不仅仅是面向最终用户的,很大一部分接口是可以令插件提供商进行调用。插件提供商通过将核心业务与自身特色服务进行整合,完成新的插件。插件注册服务是Kernel提供的一个基础服务,插件部署运行后需要向使用插件注册服务来将自身注册到Kernel当中。Kernel维护着运行中的插件列表,通过通用接口完成一系列跟插件有关的业务操作。消息服务是Kernel与Plugin交流的工具,一般来说,它基于发布/订阅模式。Kernel订阅所有Plugin发布的消息,而Plugin可以选择的订阅Kernel核心业务所发布的消息,完成自身的业务逻辑。

PLUGIN的生命周期

Plugin是服务组件提供商,将Kernel的核心业务与自身特色服务整合提供出去的增值服务。Plugin在模型中的主要有如下几个状态:

  1. 未安装(NONE): 插件未被SaaS运营商使用。
  2. 已下载(DOWNLOADED): 插件被SaaS运营商下载,但未部署。
  3. 启动中(STARTING): 插件正在启动过程中。
  4. 已启动(ACTIVE): 插件已经启动,可以正常提供服务。
  5. 正在停止(STOPPING):插件正在关闭过程中。

在SaaS运营商运营过程中,5种状态可能会经常进行转化,形成了插件的生命周期,它的演化过程如下图所示:

插件的生命周期.png

图中虚线表示自动执行的过程,实线表示SaaS运营商进行的操作。运营商的操作有:

1.     下载(download)操作

下载操作需要运营商经过插件提供商的授权,从某个插件托管平台上将插件下载到本地的环境当中。

2.     更新(update)操作

更新操作只能在已下载状态中执行,运营商在插件提供商授权的情况下将本地的插件升级到最新的插件版本。

3.     移除(remove)操作

SaaS运营商主动移除本地插件,并向插件运营商发起解除授权请求,终止插件使用协议。

4.     启动(start)操作

SaaS运营商将本地已下载的插件部署到生产环境当中,对外提供服务。这个操作涉及到三个状态分别是:已下载、启动中和已启动。

5.     停止(strop)操作

SaaS运营商将已部署的插件解除部署,停止对外提供服务。这个操作同样涉及三个状态分别是:已启动、停止中和已下载。

相关技术

为了将Kernel-Plugins模型付诸实践。我们引入了一系列技术来支撑方案的实施。主要涉及到部署层面的容器级虚拟化解决方案Docker,Java服务器端的OSGI技术等。

容器级虚拟化解决方案Docker

Docker是一个开源的应用容器引擎。开发者可以打包他们的应用以及依赖包到一个可移植的容器中,并发布到任何流行的 Linux 机器上。

与传统方式相比,Docker容器的启动可以在秒级实现。其次,Docker对系统资源的利用率很高,一台主机可以同时运行数千个Docker容器。最重要的是对开发运维人员来说仅仅通过一次创建或配置就可以在任意地方正常运行。这一点非常适合Kernel-Plugins业务模型的场景。

本文的场景中,存在着多个SaaS的运营者,一个Kernel软件供应商,多个Plugin软件供应商。运营者如果采取传统的方式部署系统的话,需要按照安装文档配置服务器,安装依赖,并维护大量的部署脚本来完成Kernel及Plugin的部署。这是一个相当令人头疼的工作。

引入Docker后,Kernel软件供应商可以将依赖及制品打包到一个镜像中,方便运营者在任意地点部署Kernel,并保证Plugin生命周期管理的基础设施可以正常提供服务。

面向JAVA的动态模型OSGI

OSGi技术是指一系列用于定义Java动态化组件系统的标准。这些标准通过为大型分布式系统以及嵌入式系统提供一种模块化架构减少了软件的复杂度。并为开发者提供了一个通用安全可管理的Java框架,能够支持可扩展可下载的应用(即bundles)的部署。

在本文中我们引入OSGI,主要基于以下考虑:

  1. OSGI具有动态更新的特性,框架能够帮助BUNDLE动态的安装、启动、停止、更新和卸载。本文服务端插件生命周期的管理就是基于OSGI动态更新的特性来实现的。
  2. OSGI使用类加载机制,与JAR包的线性加载不同,它使用bundle的委托式加载,这样类加载就不需搜索。Kernel和Plugin的启动可以更快。
  3. OSGI支持组件模型bundle,可以隐藏内部实现,并基于OSGI服务进行交互,有效的降低了Kernel与Plugins集成的复杂性。

实例分析

本节基于一个SaaS实例来展示本文的在线集成和在线演化方案。这个应用是软件生命周期管理平台onboard,它的核心服务是为软件开发企业提供基于任务和回顾的团队协作支持。

然而许多企业是不仅仅满足于使用任务来加速他们的开发进程的。比如有些团队需要代码托管服务来跟踪团队开发人员完成任务的情况。需要文档服务来为团队建立知识资源库等等。

传统的方式是将所有功能都集成到一个软件上,为所有人提供服务。但是这样成本过于高昂,运营商的客户也不一定都有这样的需求。所以引入了本文的方案开发了针对onboard的插件商店,解决了onboard产品插件的发布、安装、升级等问题。

系统架构

绘图1.png

上图是onboard插件商店的系统架构图。

系统引入了docker容器引擎,方便运营商对onboard进行部署。我们使用docker分别将Kernel的前端制品和后端制品及相关依赖打包成Docker镜像。

在这里我们采用了前后端分离的方案,这是业界主流的一种做法,将模板渲染工作、URL路由、Session保持等工作交由前端进行。服务器端只负责提供可靠的REST API,当用户操作系统进行业务操作之时,前端会将请求转发给后端由后端完成业务逻辑再将结果展示给用户。这样做的好处是从部署上来说降低了系统的复杂性,加快了系统的启动速度。从开发上来说,前端部分的服务端代码量很少,这样带来的好处是静态资源的刷新速度更快,对于前端开发者来说可以很快的看到改动的结果,提高开发效率。

在插件的管理上,我们在前端和后端容器中分别引入了插件管理器这个组件,通过它我们会去管理前端和后端插件的生命周期。前端插件在此系统中主要为静态文件,包含javascript, css以及html模板。后端插件就是一个Bundle。当用户想要改变某插件的状态时,他需要使用管理界面发起一个变更请求,前端进程接收到请求以后会委托插件管理器改变前端插件的状态,并以REST接口调用的方式通知服务端来完成后端插件状态的更改。任何插件状态的改变都是原子操作。

插件生命周期的管理

前文中, 我们介绍了插件生命周期的各个状态:未安装、已下载、启动中、已启动和正在停止。在4.1中我们又介绍了该方案的基本架构。本节将介绍系统是如何通过该架构完成插件生命周期管理的。

插件制品的形式

在本例中,前端插件的内容为静态文件和thymeleaf模板文件。将正确的静态文件和模板文件分别放置在static目录和templates目录中,将二者打包成一个zip文件,就完成了一个前端制品。

后端制品的形式是OSGI的一个BUNDLE,插件开发者需要使用Maven及felix插件或其他构建工具将插件的后端工程打包成一个Jar包。这个jar包就是该例的后端制品。

插件开发者需要将前端制品和后端制品上传到插件商店当中,并指定版本号,以供SaaS运营者使用。

下载、移除、更新插件

绘图12.png

运营者想要将一个插件安装到系统中,首先要经历的过程就是下载插件。前后端插件管理器会向插件商店服务器请求最新版本的下载到本地。下载完成后,会计算下载文件的MD5值并与远端服务器数据进行比对。如若前后端一方下载失败,系统则会重试,直到前后端均下载到了正确的插件。

当插件运营者发起卸载插件的请求时,如果插件处于已下载状态时,前后端的插件管理器会将本地的插件文件及配置删除。

每当插件处于DOWNLOADED状态并向切换到STARTING状态前,系统都会向插件商店服务器请求插件对应版本的MD5值,并与本地进行比对,如若不一致则会执行Update操作。将本地对应版本的插件制品删除,并执行download操作。

另一方面如若SaaS运营商发起了一个插件升级的请求,插件则会同样发起更新请求删除本地版本的插件,下载最新版本的插件,并记录到本地配置当中。

启动、停止插件

绘图11111.png

插件的启动和停止的过程涉及到插件多个状态的改变。

当SaaS运营商发起插件启动请求时,前后端插件管理器会检查本地插件文件的完整性,如若不完整会执行update操作。检查无误以后,插件管理器会将插件的状态置为启动中,并将后端制品放入Virgo服务器的pickup目录中,前端制品放入前端的静态及模板资源扫描目录。Virgo服务器会去扫描pickup目录,当引入新的jar包,OSGI框架会尝试将Bundle启动,启动完成后会回调后端插件管理器的接口,完成后端插件的启动,后端启动后,后端插件管理器返回成功状态给前端插件管理器,前端插件管理器会将通知前端模板引擎和静态资源解析器将页面资源引入到线上系统中。并将插件状态置为ACTIVE。

当插件状态在Starting到ACTIVE的任一环节出错的话,插件管理器会尝试回滚操作,包括删除静态和模板文件、删除Bundle文件、停止Bundle、解除前端模板引擎和静态资源解析器对该插件静态资源的渲染。

当SaaS运营商发起插件中止请求时,会首先删除后端插件的Bundle文件,Virgo服务器扫描到删除事件后,会尝试停止Bundle,停止后会回调后端插件管理器,插件管理器会去通知前端插件管理器删除静态及模板文件,解除前端模板引擎及静态资源解析器对插件目录的扫描,中止提供服务。同样,插件的中止操作也是原子操作。

KERNEL与PLUGIN的交互

除了对插件生命周期的管理外,插件还需要通过与Kernel的交互完成它的业务逻辑。在本例中采用了如下几个方面的交互。

服务端插件注册

插件注册是插件与内核交互的最基本手段,通过将插件的一些实现Kernel接口的实体注册到Kernel当中,就可以通过接口将插件的信息织入到Kernel当中,完成扩展。本例中使用了OSGI的监听技术来支撑这一实现,在Kernel中,我们会监听使用某接口定义的OSGI服务的建立事件,并维护一个接口列表。

<list id="activityGenerators" interface="com.onboard.service.activity.ActivityGenerator">
<listener bind-method="addActivityGenerator" unbind-method="removeActivityGenerator" ref="activityRecorderBean" />
</list>

当插件以这个接口声明OSGI服务时,Kernel就会得到这个接口实例,这样在一些特定的业务逻辑中,我们也可以通过接口获取到插件的相关信息。

引用Kernel服务

除了将插件注册到Kernel之外,Kernel提供的服务也可以注入进插件中去。我们在Kernel中完成了某个业务逻辑就可以将之声明为一个OSGI服务暴露出去:

<service id="activityService" interface="com.onboard.service.activity.ActivityService" ref="activityServiceBean" />

在插件中,可以将Kernel的服务引入到bundle中,并将之与插件的业务逻辑进行组合:

<reference id=" activityService" interface="com.onboard.service.activity.ActivityService " />

前端插件的注册

在业务上,除了后台业务接口涉及到扩展外,前端的页面展示也会因为插件的启动和停止而有所不同。在本例中,我们通过Javascript完成前端插件的注册与解除。

在Kernel中,我们定义了几种形式的扩展点,如:侧边栏插件扩展,账户页面插件扩展等。并定义了一系列的注册方法。

this. registerSidePlugin = function(option) {
      var newPlugin = {
          title: '',
          'ui-sref': undefined
      };
      for (var key in newPlugin)
          if (option[key] != undefined)
              newPlugin[key] = option[key];
      accountPlugins.push(newPlugin);
      return newPlugin;
};

在Plugin中,则会调用kernel的注册方法,来将自身注册到kernel当中。

pluginService.registerSidePlugin({
      title: '文档',
      icon: 'fa-file-text',
      'ui-sref': 'company.project.documents'
});

在安装插件的过程中,插件管理器会令前端模板渲染器渲染出引入所有插件script文件的html,在用户访问网站时浏览器会按顺序加载引入的js。当引入到插件的js时,就会执行注册方法,将插件注册到kernel当中。Kernel会根据注册的插件列表完成前端页面的展示。