Cloud Foundry中 JasperReports service集成

Posted on 2013-08-03 21:25  冰天雪域  阅读(359)  评论(0编辑  收藏  举报

       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中。





 

转载清注明出处。

 

这篇文档更多出于我本人的理解,肯定在一些地方存在不足和错误。希望本文能够对开始接触Cloud Foundry中service的人有些帮助,如果你对这方面感兴趣,并有更好的想法和建议,也请联系我。

我的邮箱:shlallen@zju.edu.cn

新浪微博:@莲子弗如清 

 

 

 

Copyright © 2024 冰天雪域
Powered by .NET 9.0 on Kubernetes