Cloud Foundry作为业界第一个开源的PaaS解决方案,正越来越多的被业界接受和认可。随着PaaS的发展,Cloud
Foundry顺应潮流,充分发挥开源项目的特点,到目前为止,已经支持了大批第三方技术和服务。
在开发框架的支持上,Cloud Foundry支持如今很多主流的开发框架,比如:Spring、Lift、Grails、Play、Rails、Sinatra、Node.js、PHP、Python等。另外,Cloud
Foundry还有供用户定义自身代码框架的接口提供,大大扩展Cloud Foundry自身的开发框架。
另一方面,Cloud Foundry已集成较多第三方服务,以供用户扩展应用,比如:数据库服务MySQL、Postgresql、MongoDB、Neo4j、Redis等;存储类服务Vblob、filesystem等;其他类型服务RabbitMQ、ElasticResearch等。另外,Cloud
Foundry还有提供用户自定义添加系统服务的接口,大大增强平台本身对服务的可扩展性。
1. JasperReports简介
报表是企业或组织的基本业务需求,它可以帮助企业或组织管理部门更感观地访问数据,处理数据,查阅数据,并依此深入洞察企业活组织的运营状态。报表往往被认为是企业或组织发展的强大驱动力。
在工业界,JasperReports无疑是一款功能强大,广受欢迎的报表引擎。它可以通过报表模板的设计以及数据源数据的填充,灵活生成诸如PDF、HTML、XML等多种格式的报表。由于JasperReports是用Java开发的开源程序库,因此开发者可以方便快捷的将JasperReports引入开发的应用程序,以完成应用中生成报表模块的功能。
正是由于JasperReports报表引擎的功能强大与应用方便,对于开放的PaaS平台来讲,将JasperReports集成进PaaS平台就变得十分具有意义。它不仅满足应用对于报表的需求,还大大丰富了平台本身提供的功能。
2. JasperReports service与app形式比较
将JasperReports集成进CloudFoundry,可以有两种方式:第一种是将JasperReports设计成一项服务,以服务的形式提供给应用来访问;第二种是将JasperReports设计成一个应用程序,直接部署进Cloud
Foundry,负责报表的生成。
虽然JasperReports的service形式和app形式在Cloud
Foundry中都可行,但是这两种形式在功能和性能上却有着很大的差异。
第一,在功能上,JasperReports的service形式要比app形式强。当用户需要完成一个系统的报表工作时,如果将JasperReports设计成app形式,那么JasperReports作为一个独立的应用程序时,不能和用户的主应用程序融合在一起。可见报表的生成需要用户人为手动操作,也就是说完成一个报表的生成,需要在Cloud
Foundry使用两个不同的应用。这种app的形式,大大违反了Cloud Foundry的可操作性,加大了用户使用的难度,使得应用功能的实现变得繁琐不便。
但是如果将JasperReports设计成一项服务集成进Cloud Foundry的话,JasperReports就完全可以和应用程序分离开来,并以服务的形式提供给app应用使用。当在Cloud
Foundry上部署的app应用需要JasperReports功能时,只需要创建一个JasperReports服务实例,并将整个服务实例与整个app应用绑定,即可由应用程序来决定是否访问该JasperReports服务实例。这种service的形式,大大简化了JasperReports报表功能的业务逻辑,也简化了用户的操作难度,但也不可避免的加大了应用开发者的开发难度。
第二,在性能上,JasperReports的service形式,最大限度地将报表负载转移到Cloud
Foundry的service节点上,实现报表负载与app应用的完美分离,大大缓解app应用的负载压力,降低Cloud
Foundry中DEA的负载压力。由于Cloud Foundry的service节点与DEA节点之间低耦合,service节点负责同种类型service的负载,而DEA节点负责该节点上所有app的运行,因此当JasperReports以app形式存在时,进行数据量极大的报表生成时,DEA节点的CPU、内存等负载将大幅提高,不仅影响自身应用的性能,同时还有可能影响同DEA节点的其他app应用性能。
3. JasperReports中service概念与Cloud Foundry中service概念对应
Cloud Foundry支持的现有服务中,大多数都属于传统的关系型数据库以及NoSQL数据库。在这两类数据库服务中,提供service服务,相当于由数据库server提供数据存储的服务,换言之,也就是由数据库server端创建database,然后将该database转交给Cloud
Foundry的app应用使用。
MySQL被认为是一项Cloud Foundry中较为传统的service,以下则讲述该MySQL数据库,描述MySQL在Cloud
Foundry中service instance的概念。JasperReports的集成以此为参考,根据相关概念借鉴的可行性,抽象出JasperReports在Cloud
Foundry中的service 概念。
以MySQL为例,MySQL数据库向CloudFoundry中的app应用提供MySQL服务,也就是MySQL数据库的server端可以创建MySQL
db并将该db的URL传递给app应用,以供app应用使用。在Cloud Foundry中,MySQL的service
instance的概念对于与一个MySQL的db,而Cloud Foundry对于MySQL服务所有的管理操作,也只限于db这一层。
为实现将JasperReports作为一项服务集成进CloudFoundry,首先需要将JasperReports以一个server的形式存在于Cloud
Foundry中。在调研了JasperReports的相关产品之后,决定选择JasperReports
Server作为一个service节点部署于Cloud Foundry。
JasperReportsServer是一款单节点且可嵌入的报表server,它由JasperSoft公司开发,并且开源,支持AGPL协议。它提供的报表业务和分析业务,可以被嵌入至web应用或移动应用中。Web应用可以通过RESTful和SOAP的形式访问JasperReports
Server的资源。
选择合适的server端之后,需要将JasperReports中service的概念清晰化,并将其与CloudFoundry中传统的service概念相对应。
由于JasperReportsServer在内部是通过清晰的文件系统来存储资源的,在最终的报表生成时,通过调用指定路径下的资源(如:报表模板jrxml文件,数据源,报表设置等)来完成。为方便起见,将JasperReports的service
instance设计成JasperReports Server中文件系统的一个文件夹节点,而用来生成报表的资源(jrxml文件,数据源定义,报表设置等)都存放于该节点下。
以下为JasperReports 与MySQL service instance的对比与参照:
|
JasperReports |
MySQL
|
service
instance |
JasperReports
Server文件系统节点 |
MySQL
database |
instance
内资源 |
数据源定义,jrxml文件,报表设置 |
数据表 |
instance
credentials |
host,port,username,password,node
locatoin |
host,port,username,password,dbname |
下图为JasperReports在Cloud Foundry中service
instance的具体表现形式:
其中,JasperReports service instance是名为bd15e871-7b8d-41d8-a4a9-f6d67eb056dc的文件夹节点,该JasperReports
serviceinstance内资源有数据源MonthlyReports,报表模板monthlyreports.jrxml,以及报表配置reports。
当app应用需要访问该JasperReports service instance的时候,只需按要求访问路径/root/SHL/bd15e871-7b8d-41d8-a4a9-f6d67eb056dc下的reports文件,即可生成最终报表。
4. JasperReports Service 具体实现
JasperReports service的实现首先需要完成JasperReportsservice中的数据表现形式以及存储形式。由于在上一部分以及抽象归纳出JasperReports
service的service instance定义,service instance内资源描述以及service
instance的credentials信息,因此,对于JasperReports service的存储就更清晰,更容易设计。
在JasperReports service 中,我们使用JasperReports
Server的文件系统来存储JasperReports service的service instance。另外,credentials信息的存储,使用Cloud
Foundry对传统型服务的credentials的存储方式,在Service Node处,使用sqlite数据库存储,Service
Gateway处使用内存存储,Cloud Controller处,使用postgres数据库存储。
CloudFoundry中和service相关的模块主要有CloudController、DEA和NATS。如下图,为简化的Cloud
Foundry框架图。
从上图的service模块中,可以看出service模块主要由Service
Gateway和Service Node构成。从模块功能来看,Service Gateway主要负责接收Cloud
Foundry对于service的管理请求,处理后发送给Service Node执行操作;而Service
Node则主要负责接收Service Gateway处理后的请求,并对service instance执行请求操作。
将JasperReports以service形式集成进Cloud Foundry,则需要开发设计JasperReports
service的Service Gateway和Service Node。
4.1. JasperReports Service Gateway实现
在Cloud Foundry中,Service Gateway主要负责接收从Cloud
Controller发来的service管理请求,其中包括provision、unprovision、bind、unbind等操作。
在开发过程中,JasperReports Service Gateway主要是继承vcap-service-base中的Base:Gateway类,并且在启动JasperReports
Service Gateway创建一个JasperReports_gateway类,并启动。
关于Cloud Foundry中JasperReports Service
Gateway的实现过程较简单,大部分的工作全都由Base:Gateway来完成。所以,集成过程中,JasperReport创建了一个启动JasperReports
Service Gateway的文件,由Cloud Foundry来调用。文件目录为:~/cloudfoundry/vcap/services/jasper/bin/jasper_gateway,文件内容为:
4.2. JasperReports Service Node实现
在Cloud Foundry中,Service Node主要负责管理service,其中包括provision、unprovision、bind、unbind等操作,但是Service
Node却不是最终service的提供者,service的提供者为该类service的server。
以MySQL为例,MySQL Service Node负责接收Service
Gateway通过NATS发来的请求,并根据请求做相应的管理操作,真正的service instance位于MySQL
server端。
类比于JasperReports,JasperReports Service
Node需要实现接收Service Gateway发来的请求,并根据请求做相应的管理请求,真正的service
instance位于该节点处的JasperReports Server端。
ENV["BUNDLE_GEMFILE"] ||= File.expand_path('../../Gemfile',__FILE__) require 'bundler/setup' require 'vcap_services_base' $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'jasper_service/jasper_provisioner' class VCAP::Services::Jasper::Gateway < VCAP::Services::Base::Gateway def provisioner_class VCAP::Services::Jasper::Provisioner end def default_config_file File.join(File.dirname(__FILE__), '..', 'config', 'jasper_gateway.yml') end end VCAP::Services::Jasper::Gateway.new.start |
4.2.1. JasperReports Service类图及类功能
整个Service Node节点从代码上来分析,可以分为两个层次结构,一部分是Cloud
Foundry提供的Base模板,这部分存在于包vcap-service-base中,该包以gem包的形式被下载至service
node节点gem环境下,这部分已经帮助service开发者完成了很多service的开发工作;另一部分是JasperReports
Service Node部分,这部分主要针对相应的服务,进行相应的操作,需要service开发人员自行设计与编写相应代码。
以下是JasperReports的 Service Node的类图:
上图中vcap-service-base层次的类都是系统提供的Node节点模板,自定义的JasperReports
Service通过继承Nodebin和Node两个类,并实现其中预留的接口,最终完成JasperReport
Service集成进入Cloud Foundry。
下表主要介绍了每一个类实现的功能。
类名 |
功能 |
Base::Base |
提供了整个Service通信框架,主要是提供了NATS的连接与初始化。 |
Base::Node |
实现了Node节点功能模板,预留了大量的抽象方法,便于Service开发者实现自定义的功能。 |
Base::NodeBin |
主要完成Node节点的初始化配置,然后将配置参数传入Base::Node。 |
Jasper::NodeBin |
需要实现Base::NodeBin下配置文件读入,以及所有的参数的初始化。 |
Jasper::Node |
需要实现Base::Node下的抽象方法,实现Node节点的实例创建、管理等操作。 |
4.2.2. JasperReports Service Node启动流程
以JasperReports Service为例,以下是该类型Service
Node的启动流程:
创建JasperReports:Nodebin实例过程中,主要继承于Base:Nodebin,所以JasperReports:Nodebin继承了Base:Nodebin所有功能。
初始化参数过程中,涉及到Node节点运行过程中需要的所有配置参数,都会从配置文件中读取,并完成初始化。初始化的配置文件变量与初始值如下:
--- plan: free capacity: 100 local_db: sqlite3:/var/vcap/services/jasper/jasper_node.db mbus: nats://localhost:4222 base_dir: /var/vcap/services/jasper/ index: 0 logging: level: debug pid: /var/vcap/sys/run/jasper_node.pid node_id: jasper_node_1 host: 10.10.17.36 port: 8081 user: jasperadmin password: jasperadmin supported_versions: ["1.0"] |
创建JasperReports:Node实例,主要是为了创建一个最终会对service进行操作的类,随后,所有一切关于service的管理操作,都由JasperReports:Node这个类接收请求,并执行。
以上三阶段的操作都是在文件jasper_node文件中实现,具体代码如下:
#!/usr/bin/env ruby # -*- mode: ruby -*- # Copyright (c) 2009-2011 VMware, Inc. ENV["BUNDLE_GEMFILE"] ||=File.expand_path("../../Gemfile", __FILE__) require 'bundler/setup' require 'vcap_services_base' $LOAD_PATH.unshift(File.expand_path("../../lib",__FILE__)) require "jasper_service/jasper_node" class VCAP::Services::Jasper::NodeBin <VCAP::Services::Base::NodeBin def node_class VCAP::Services::Jasper::Node end def default_config_file File.join(File.dirname(__FILE__), '..', 'config', 'jasper_node.yml') end defadditional_config(options, config) options[:host] =parse_property(config, "host", String) options[:port] =parse_property(config, "port", Integer) options[:user] =parse_property(config, "user", String) options[:password] =parse_property(config, "password", String) options end end VCAP::Services::Jasper::NodeBin.new.start |
连接至NATS,主要完成Service Node与Cloud Foundry中消息中间件通信的工作。
订阅主题,主要完成向消息中间件NATS订阅相应的主题,以便NATS接收到类似主题的时候,会将请求转发给该Service
Node。
添加周期性任务,就是在Node节点正常运行中需要定期执行的工作,包括向Service
Gateway发送本节点信息,以及想Component组件注册信息。
4.2.3. JasperReports Service Node运行流程
JasperReports Service Node在初始化完成之后,在运行过程中,除了添加两次周期性任务之外,最主要的任务就是负责JasperReports
service instance的管理,其中包括provision、unprovision、bind、unbind等。
以下主要从provision和bind两个操作来说明JasperService
Node关于操作请求的运行流程。
4.2.3.1. Provision a JasperReportsservice
instance
创建JasperReports service instance使用的主题是provision主题,创建完毕的serviceinstance并未与app应用进行绑定。
一个JasperReports service instance的创建流程大致如下图:
(1). 用户使用vmc等管理工具向Cloud Controller发送创建JasperReports
service instance的请求;
(2). Cloud Controller根据请求要求对应的JasperReportsService
Gateway来处理该任务;
(3). JasperReports Service Gateway调用provision_service()方法从管理节点中选择合适的节点,并通过NATS向该节点发送provision;
(4). Base:Node中on_provision方法接收这个主题的请求;
(5). 调用provision方法创建JasperReports service
instance;
(6). 执行创建一个JasperReports service instance;
(7). 一旦创建成功JasperReports:Node返回一个credentials信息给Base:Node;
(8). Base:Node返回一个正确的编码信息给Gateway,以示创建成功,另外将credentials信息返回给Gateway。
4.2.3.2. JasperReports:Node 中provision方法
添加自定义的JasperReportsservice进Cloud Foundry,其中provision方法显得尤为重要。
Provision方法主要是由JasperReports:Node执行,执行的对象是JasperReportsserver。
由于JasperReportsserver是一个基于web service的server端,并且支持RESTful和SOAP请求,因此可以在provision方法中实现向JasperReports
server发送RESTful的HTTP请求,并通过请求的response信息来确定provision的成功与否。
关于HTTP请求中所需的host、port、username、password、URL信息均在初始化参数时,从配置文件中读得,并存在于实例变量中。
另外,在创建一个JasperReportsservice instance的时候,需要使用一个ruby的数据库映射框架。创建的service
instance会存入相应的数据库,也就是说,在JasperReports Node端会将创建service
instance的信息进行备份,以备后来bind以及其他请求的需要。
下图为provision过程中JasperReports:Node与JasperReportsserver的交互示意图。
在Service Node接收到provision aservice请求的时候,惠子啊provision方法中调用方法create_resource方法来实现,该方法的具体代码实现如下:
def create_resource(provisioned_service) name= [:name].map { |field|provisioned_service.send(field) } name=name[0] begin @logger.debug("start creatingresource #{name}") response_login = RestClient.post("http://#{@host}:#{@port}/jasperserver/rest/login", {:j_username=> @user, :j_password=> @password} ) if response_login.code != 200 @logger.debug("401:Unauthorized by JasperReports Server") return false end resource_descriptor="<resourceDescriptorname='"+name+"' wsType='folder' uriString='/SHL/"+name+"'isNew='false'> <label>"+name+"</label> <resourcePropertyname='PROP_PARENT_FOLDER'> <value>/</value> </resourceProperty> </resourceDescriptor>" @logger.debug("resource_descriptor:#{resource_descriptor}") #get resourceDescriptor before generatethe report response_resource =RestClient.put("http://#{@host}:#{@port}/jasperserver/rest/resource", resource_descriptor, {:cookies =>{"JSESSIONID" => response_login.cookies["JSESSIONID"]}} ) if response_resource.code == 201 @logger.debug(" 201: Resourcecreated successfully") return true else @logger.debug(" Failedcreating resource }") return false end end end |
4.2.3.3. JasperReports:Node 中bind方法
在Cloud Foundry中,bind操作意味着一个app应用与一个serviceinstance执行绑定,以便app应用在运行过程中,可以通过该service
instance的credentials来访问这个service instance。
app应用和JasperReportsservice instance的绑定过程类似于JasperReports
service instance的provision,也是通过NATS接收到主题,并由JasperReports:Node进行相应的操作。
bind方法也是在CloudFoundry中预留的开发接口,开发者可以根据自己的需求灵活编写这个接口。
JasperReportsservice 的bind操作的具体流程主要如下:
(1). 从数据库中找出存储的JasperReports service
instance;
(2). 创建一个数据库存储绑定的app的信息,以备份信息;
(3). 将结果信息返回给JasperReports Service Gateway。
当bind请求的response到达ServiceGateway之后,Service
Gateway将credentials信息和app应用做成一个droplet。当Cloud Foundry启动这个app应用时,相应的credentials信息已被写入该app应用的环境变量中,最终Cloud
Foundry中的app应用通过这些信息直接访问JasperReports service instance。
以下为bind的具体代码实现:
def bind(name,binding_options, credential = nil) instance = nil if credential @logger.debug("binding has a credential") instance =get_instance(credential["name"]) else @logger.debug("binding has NO credential") instance = get_instance(name) end gen_credential(instance) end |
4.2.4. 配置CloudFoundry调用JasperReports
service的 Service Gateway与Service Node启动脚本
JasperReportsservice的Service Node和Service
Gateway设计完成并实现后,需要将这两个组件的启动添加到Cloud Foundry。
这一部分所需要做的工作并不多,主要还是让Cloud Foundry在启动组件的时候,检测到JasperReports
service的Service Node以及Service Gateway。并到相应的目录下,找到Service
Node和Service Gateway的启动脚本并执行,具体的脚本启动文件的目录为:~/cloudfoundry/vcap/services/jasper/bin/jasper_gateway和~/cloudfoundry/vcap/services/jasper/bin/jasper_node。以上为启动流程,停止等操作也是相同的原理。
CloudFoundry中注册的组件在vcap_components.json文件中,路径为:~/cloudfoundry/.deployment/devbox/config/vcap_components.json。在其中添加JasperReportsservice的Service
Node和Service Gateway。
另外在执行的时候,CloudFoundry会执行vcap_components.rb文件。在该文件中,有具体启动时,需要启动的组件,则在该部分添加需要的Service
Node和Service Gateway。具体代码如下:
## services: gateways & nodes %w(redis mysql mongodb rabbitmq postgresql vblob neo4j memcached couchdb elasticsearch filesystemjasper).each do |service| ServiceComponent.register("#{service}_gateway") end %w(redis mysql mongodb rabbitmq postgresql vblob neo4j memcached couchdb elasticsearchjasper).each do |service| ServiceComponent.register("#{service}_node") end |
5. 访问JasperReports Service的app应用案例
为体现JasperReportsservice 在Cloud Foundry中的可用性,需要4个步骤来体现:
(1). 用户发送provision a JasperReports service
的请求,Cloud Foundry创建一个JasperReportsservice instance;
(2). 用户对该JasperReports service instance进行设计,如添加数据源,添加报表模板和设置报表等;
(3). 用户发送请求,将该JasperReports service
instance和部署在Cloud Foundry上的app应用进行绑定;
(4). App应用运行过程中访问该JasperReportsservice
instance。
由于CloudFoundry很好的支持Rails框架的应用程序,因此为验证JasperReports
service的可用性,我编写了一个rails应用部署在Cloud Foundry上,并和provision完毕并内容设计完毕的JasperReports
service instance进行绑定,最终成功完成app应用对JasperReports service
instance的访问。
以下为简单的应用部署示意图:
1.通过CloudFoundry创建一个jasper的service instance,该演示案例中service
instance名为:jasper-ca485 。
2.开发用户进入JasperReportsServer中进行service
instance设计,包括上传数据源和报表模板,还有最终报表的配置,简要操作流程如下:
首先添加数据源,如图:
然后上传报表模板:
最后需要配置报表,主要为配置报表模板文件以及配置数据源关联,如下图:
通过以上步骤,关于JasperReportsservice instance就配置全部完成,接着需要app来访问。
3.将该JasperReportsservice instance与app进行绑定,绑定过程如下:
4.绑定完毕之后,app就可以运行了,下图为app应用的运行情况示意图:
5.下图为点击链接“Generatea report”所得到的结果:
6.当点击上述链接之后,可以在相应的app应用程序目录下产生一个报表文件report.pdf
由于bind过程中,CloudFoundry只是将service instance的credentials信息写入app的环境变量中,所以在app运行的时候,很重要的一步就是要将app环境变量中关于credentials的信息读取出来,并供app应用需要的时候访问。
以下为以上演示案例中读取环境变量的ruby代码实现:
start = ENV["VCAP_SERVICES"].index('credentials') original_length=ENV["VCAP_SERVICES"].length credentials=ENV["VCAP_SERVICES"][start+14..original_length-5] elements=credentials.delete('"').split(",") a={} elements.each do |element| details=element.split(":") a[details[0]]=details[1] end @host = a["host"] @port = a["port"] @name = a["name"] |
6. Cloud Foundry中通用service集成
上文已经谈到CloudFoundry已经集成了很多第三方的中间件服务,并且提供了用户添加自定义服务的接口。随着Cloud
Foundry的发展,开发者势必会将更多的服务集成进Cloud Foundry,以供app使用,也扩展了app的功能。
本部分主要描述通用service集成进入CloudFoundry所需要做的设计以及实现。
6.1. service概念的对应
将通用的service类型集成进CloudFoundry,需要做的第一个也是最重要的工作,就是须将待集成service的多种概念与Cloud
Foundry中对于service的概念进行对应。
CloudFoundry中这些概念包括:service instance,credentials,provision和bind等。
CloudFoundry中service概念有很多,现将以上四种最为主要的概念进行具体的阐述:
service instance
serviceinstance是一项服务的具体事例。它是Cloud Foundry对于service操作的最终载体,存在于Cloud
Foundry的Service Node中,上一层的管理者为Service Node。在实际应用过程中,service
instance由Cloud Foundry上运行在DEA中的app访问,并且一个service instance可以被多个app同时访问。通用service的集成的首要任务就是,在通用service中抽象出service
instance的具体表现形式。
credentials
credentials是serviceinstance的认证信息。当创建一个service
instance的时候,Cloud Foundry会为这个service instance创建认证信息,也就是credentials,它的作用是:作为app访问这个service
instance所必需的认证信息。在创建service instance的时候,Cloud Foundry将产生的credentials存在Cloud
Controller,在执行app与service instance的时候,Cloud Foundry会重新生成一个credentials信息,然后由Cloud
Controller在绑定app的时候,将这个credentials写入该app的环境变量中,以供app访问,app通过手持credentials信息直接通过RESTful接口,访问位于Service
Node的service instance。通用service的集成中,credentials起到使得service
instance之间互相独立的作用,另外还起到app访问service instance的凭证信息。
provision a service
provision a service是指,在Cloud Foundry中在相应的Service
Node上创建一个service instance的过程。其中主要包括两个方面的操作:第一,在Service
Node创建service instance;第二,将service instance的credentials的信息传递给Cloud
Controller,并对该数据进行持久化。通用service 的集成过程中,首先要实现的就是provision
a service的工作,只有实现provision,才会有service instance的概念,并有之后对于service
instance的种种操作。
bind a service
bind a service是指,在CloudFoundry中app应用需要使用一项service时,app发送请求绑定一个或多个service
instance,并最后完成绑定的整个过程。bind a service是app访问service instance之前最后一步Cloud
Foundry接管的操作。bind a service具体的操作是Cloud Foundry将serviceinstance的crdentials信息在app应用打包的时候,写入app的环境变量中,最终由app应用启动会被读取,app通过这个crdentials信息直接访问service
instance。在通用service的集成过程中,bind是最为重要的步骤之一,只有bind成功后,service
instance才有存在的意义。
6.2. 通用service的迁移
在明确了CloudFoundry中service的概念之后,紧接着就是将通用service向Cloud
Foundry中迁移的问题。
在Cloud Foundry中,将通用service集成进来,有两个方面需要设计实现:第一,service数据的表现形式与存储形式;第二,service整体框架的设计与实现。
6.2.1. Service数据表现形式与存储形式
Service作为服务存在于CloudFoundry中,有很多种不同的类型。每一种类型的service在实现过程中,service
instance都会以某种表现形式存在,Cloud Foundry对于service的操作全部都是限于这个service
instance,关于service instance内部的具体操作,都是由app在访问这个service
instance过程中来完成。
传统的关系型数据库中,serviceinstance的表现形式就是关系型数据库的一个db。这个db的创建由关系型数据库的server来创建。
这里db作为一种serviceinstance的表现形式,在Cloud
Foundry中大多数的数据库服务中都有体现,不论是关系型数据库或者是NoSQL数据库。db的表现形式,只是Cloud
Foundry中最常用的service表现形式,另外其他的service表现形式还有很多,也可以由Cloud
Foundry service集成人员自行定义。
Service数据表现形式定义完备之后,还需要设计完成Cloud Foundry中service相关信息内容的具体存储形式。
首先,设计实现serviceinstance的存储,是Cloud Foundry中关于service存储的首要任务。Service
instance的存储形式,主要是提供一个可靠的环境供Cloud Foundry中app的访问。传统的关系型数据库以及NoSQL数据库都是以db为表现形式,以db形式存储于存储介质中,具体的组织由数据库server端接管并存储。另外,service
instance可以根据应用场景的不同,根据抽象出的service instance概念,将service
instance存储于其他的系统中,比如某些文件系统中等。
其次,service相关信息的存储,还包括在service操作过程中产生的重要信息的存储。例如,在service
instance创建完毕之后,产生的credentials需要在多个地方进行存储。在传统的关系型数据库及NoSQL数据库中,credentials在Service
Node,Service Gateway以及Cloud Controller处均有存储。其中,credentials在Service
Node处的存储是另外维护一个db来存储所有service instance的crdentials,而在Service
Gateway处的存储是直接存储在内存中的一个hash队列中;最终在Cloud Controller处也会对credentials信息进行持久化,存储在Cloud
Controller的postgres数据库中。
6.2.2. Service框架实现
在Cloud Foundry中集成通用service的框架实现主要包括两个方面:ServiceNode实现与Service
Gateway实现。当以上两个框架设计实现完毕之后,还需要将这个通用service的Service Node与Service
Gateway作为Cloud Foundry的两个组件,将这两个组件的启动添加至整个Cloud Foundry的启动脚本中,以便Cloud
Foundry的自动化启动。
6.2.2.1. Service Node实现
ServiceNode的功能主要是实现接收Service Gateway的请求,并最终向service
server发送最终关于service instance的操作请求,并将操作结果保存并返回给Service
Gateway。
ServiceNode的实现主要包括两个层次,一个层次是该类型service的Service
Node模块实现,另一个层次是所有service的基类Service Base的实现。
首先关于基类ServiceBase的实现,通用service继承开发者不需要做很多,在Cloud
Foundry的安装过程中,基类已经以Gem包的形式被安装至该类型Service Node的节点处。
要集成的service的Service Node则须集成开发者自行设计并实现。在该部分的设计实现中,除了基本的配置初始化,添加周期任务,连接消息中间件只为,最为重要的就是如何与更底层的service
server的通信,这体现在provision与bind等方法的具体实现上。如果底层service server对外暴露接口,则调用接口的具体实现就在provision和bind等方法中。
6.2.2.2. Service Gateway实现
ServiceGateway的功能主要是从Cloud Controller处接收对于service
instance的操作请求,将请求进行初步处理,通过处理后的结果,给相应的Service Node发送对于service
instance的操作请求。
ServiceGateway的实现主要包括两个层次,一个层次是该类型service的Service
Gateway模块实现,另一个层次是所有service的基类Service Base的实现。
所以通用service的ServiceGateway的实现,是由相应的Service
Gateway创建一个继承Service Base的类。传统的关系型数据库以及NoSQL数据库服务,由于业务逻辑的缘故,只需要继承所有的Service
Base类中的方法,即可实现所有的Service Gateway功能。
但是当继承某一项service的时候,由于service概念的差异,在ServiceGateway处具体实现的时候,并不是按着Service
Base的业务逻辑来运行,所以需要在该类型service的Service Gateway处进行重写方法,或者新增方法实现。
6.2.2.3. Service Node与Service Gateway的启动
一旦将需要集成的service的Service Node和ServiceGateway设计完成并实现后,则需要将这两个组件的启动添加到Cloud
Foundry。
这一部分所需要做的工作并不多,主要还是让Cloud Foundry在启动组件的时候,检测到集成进去的service的Service
Node以及Service Gateway。并到相应的目录下,找到Service Node和Service
Gateway的启动脚本并执行。以上为启动流程,停止等操作也是相同的原理。
CloudFoundry中注册的组件在vcap_components.json文件中,路径为:~/cloudfoundry/.deployment/devbox/config/vcap_components.json。在其中添加需要继承service的Service
Node和ServiceGateway。
另外在执行的时候,CloudFoundry会执行vcap_components.rb文件。在该文件中,有具体启动时,需要启动的组件,则在该部分添加需要的Service
Node和Service Gateway。具体代码如下:
## services: gateways & nodes %w(redis mysql mongodb rabbitmq postgresql vblob neo4j memcached couchdb elasticsearch filesystem).each do |service| ServiceComponent.register("#{service}_gateway") end %w(redis mysql mongodb rabbitmq postgresql vblob neo4j memcached couchdb elasticsearch).each do |service| ServiceComponent.register("#{service}_node") end |
7. 总结
本文主要介绍了JasperReports报表引擎,JasperReports报表引擎在PaaS平台CloudFoundry中集成的可能性,以及JasperReports作为一项service集成进Cloud
Foundry的技术实现,最后对Cloud Foundry中通用service的集成进行描述与分析。
由于JasperReports报表引擎与传统CloudFoundry中service概念存在差异性,故本文首先对于JapserReports
报表引擎的service概念进行抽象化,以符合Cloud Foundry对于service接口的要求。
本文还分析了CloudFoundry中service 模块中主要类的架构,并在这种类架构下进行集成JasperReports
service。集成过程中使用JasperReports server作为JasperReports service
instance的提供者。通过JasperReports Service Node发送基于RESTful的HTTP请求,进行创建JasperReports
service instance;随后完成了unprovision、bind、unbind等操作的代码实现。
另外,JasperReports作为一项service集成进入Cloud
Foundry,该service的备份、迁移和恢复等操作都十分具有实际意义,然而由于对JasperReports
server中service instance的备份和迁移还没有抽象出有效可行的方案,故现有的设计中暂不支持对于已创建JasperReports
service instance的备份、迁移和恢复等操作。
本文在最后阶段还总结了通用service想要集成进入CloudFoundry需要涉及的设计问题以及实现问题。首先需要完成service概念的对应,然后再完成Service
Node和Service Gateway的框架以及代码实现,最后将启动脚本嵌入Cloud Foundry中。
|