前言:

在大数据时代里,云平台的角色愈来愈吃重了。其中多层框架云平台成为主流(例如,上层Java+下层C/C++)。然而,云平台的主人只有一个,我们可能只是客人角色,有没有可能反客为主,挟天子以令诸侯呢?这就看架构师的思考技术了。


高老师陪您成长...

  云平台里的小框架设计范例_第1张图片 


ee                                              ee

欢迎访问 ==>高老师的博客网页

高焕堂:MISOO(大数据.大思考)联盟.台北中心和东京(日本)分社.总教练


ee                                              ee

云平台里的小框架设计范例

By  高焕堂


1. 认识云(端)平台

1.1 以百货公司比喻云平台

     传统的网络服务是封闭型的Client/Server架构的延伸。其中的Client与Server两端的软件程序是各自开发的,Server端开发人员撰写完整的Server端程序,而Client端开发人员则撰写完整的Client端程序并且呼叫Server端的程序。这种传统的网络服务系统,其Server端如同一座四合院,庭院深锁,外人(即Client程序)只能在大门口与四合院内的主人沟通。如今的云(Cloud)概念里,其系统架构和软件开发,大多来自传统技术的延伸,并非特别的创新。然而它有了开放心怀,不再紧闭大门、深锁庭院了,而是打开大门,提供庭院让外人进来搭帐篷露营。此外,四合院主人还愿意提供各项资源(软硬件)和服务,甚至免付费呢!

   上述的四合院比喻,还不能完整看出云所带来的商业模式和价值。开放型的四合院,也相当于目前大家常常去逛的百货公司,如下图:

图1  云平台就像百货公司

    在四合院里搭帐篷,也相当于在百货公司里开设专柜做生意。例如,你到许多百货公司处处可见到香奈儿、SK-II等化妆品专柜,如下图:

  图2  百货公司里的连锁专柜(企业)

     基于云的开放架构,Android开发者能到各式各样的云(如Google、Facebook等)平台上,开发云层上的应用程序(就如同进入别人的四合院里搭帐篷露营、也如同到各个百货公司开设连锁专柜)。于是,Android应用服务就如同SK-II的国际连锁服务。Android手机端成为连锁店的营运总部,Android手机也成为连锁店总经理的随身指挥利器,和运筹帷幄中心了。

1.2  以连锁专柜比喻特定领域的(跨)云平台

   一般而言,有两种常见的云分类:

  • 公有云(Public Cloud)-- 开放给各行业或各领域的人们进来写软件或使用服务。

  • 私有云(Private Cloud)-- 并不开放给别人进来写软件或使用服务。

如果你学过Java或C++的话,就知道类别里的函数可分为:public、private和protected共三种。如果对应到云的分类别上,将可以得到第三种云,就是:

  • 限定云(Protected Cloud),即<特定领域的云(Domain-Specific Cloud)>-- 特定领域就是特定行业,例如医疗行业、物联网行业、保险行业、网络游戏行业、×××行业等,各有其专属(Protected)范围。只开放熟悉该行业的人进来写软件或使用服务。例如,医疗领域云平台的系统架构图如下:

图3  医疗领域的(跨)云平台

   从图可以看出来,这个医疗领域云平台,是建立于多的公有(Public)云平台上,甚至,可能也有自己的私有(Private)云。所以它是一种跨云(公有及私有)的云。由于它是建立于公有云的虚拟平台上,不需要巨大投资于硬设备上,所以建置成本远低于公有云。欲理解这种新潮跨云平台的最好比喻就是:

  • 公有云,如同「百货公司」。

  • 特定领域云,如同百货公司里的 Chanel连锁专柜。

大家都知道:跨百货公司的连锁专柜也是公司,所以跨公有云的特定领域跨云平台,也是云。

2. 认识GAE(Google AppEngine)云平台

     GAE(Google AppEngine)是Google的云服务引擎,第三方应用程序开发者能开发应用程序(AP),然后放在Google服务器上执行,不需担心频宽、系统负载、安全维护等问题,一切由Google代管。只要AP每月不超过500万网页面的流量就可享受免费的待遇。GAE平台的系统架构如下图:

图4  GAE云平台架构(摘自Google公司文件)

    从上图可看到,从手机、PC、MID等众多端设备上,都能随时上网发出要求(Incoming Requests)来存取GAE上的服务。在GAE后台的AppServer里,GAE提供了API(即API Layer)来衔接你的云端应用程序(即AP)。


欢迎关注我(高老师)的新微博:@让您成为杰出架构师


3. 小强龙的云框架API-- 以GAE的SM游戏为例

    水果盘拉霸机(Slot Machine,简称SM)游戏。如下图:

图5  Android拉霸机游戏画面

     其玩法是先输入投注金额(Bet),然后拉动点击把手或点击钮来转动滚动条,滚动条会各自转动,然后随机出现不同图案,如果停定时,有出现符合相同或特定相同图案连成线者,即依其赔率而胜出。同一家游戏场里的拉霸机通常会联网,以投注额厘定累积大奖(Jackpot)金额,并随时更新累绩大奖金额,以便增加吸引性。

3.1  SM游戏的框架设计图

   这游戏软件可分为两部分:

  • 游戏(Game)端部分,也就是Android手机端的应用程序。

  • 柜台(Console)端部分,也就是GAE云层Servlet程序。  

   当玩家押注后,按下 按钮(开始加速滚动),游戏端就将「目前余额」和「押注金额」传送给GAE的柜台端程序。等待柜台端程序计算出中奖金额后,将「新余额」和「奖项级别」回传给Android游戏端(滚动开始减速),并更新游戏端的画面。其中,Android游戏端程序(ac01.java)发送HTTP来呼叫GAE云层的Servlet接口,如下图所示:

图6  拉霸机游戏的系统架构图

   Android游戏端透过HTTP和Servlet接口来传送三种讯息给GAE 云层。这三种讯息为:

  • 当玩家启动Android游戏端时,发送"Init:"讯息给GAE云层程序-- GAE就从DB里读取玩家的余额(即上回的余额),并回传给游戏端。

  • 当玩家按下按钮时,发送"Bett:amount,bet" 讯息给GAE云程序-- 此讯息附有余额(amount)和押注金额(bet),要求GAE程序决定「奖项级别(Rank)」,计算奖金和新余额,然后回传给游戏端。

  • 当玩家欲结束时,按下按钮发送"Fini:amount"讯息给GAE云层-- 此讯息附有目前余额(amount),GAE接到讯息,就依据将余额存入DB,完成时立即回复给游戏端,关闭游戏端画面。

  GAE Console端应用程序包含两个部份:Servlet模块和GM模块。GM(全名是Game Machine)类别是Console端应用程序的决策核心,例如决定游戏获奖的奖项,计算奖金等都是GM负责的任务。至于Servlet类别体系则是负责与Android游戏端的沟通任务。

Slot Machine的框架设计图(方案一)

  兹先设计(和开发)Slot Machine应用框架,如下图所示:

图7  Console端游戏框架设计图(依据方案一)

    当Android游戏端(简称SM)呼叫HttpServlet类别的Servlet接口时,会转而呼叫smConsoleStub类别的doGet()函数,此doGet()转而呼叫process()函数去解析来自Android游戏端的讯息,然后呼叫GM类别的函数,或呼叫应用程序的onInitialAmount()和onFinished()函数。

    值得留意的是,此框架设计者(即smConsoleStub类别设计者)决定了它与游戏端沟通的讯息格式(Format),例如游戏端必须使用"Init:"讯息格式、"Bett:"讯息格式和"Fini:"讯息格式。一旦框架设计者决定了沟通接口,则应用程序开发者就必须遵循这些接口,而不能更改之。此时,框架就藉由这些接口来「框住」应用程序(及开发者)了。此外,框架也决定了它与子类别间的接口,也就是决定了onInitialAmount()和onFinished()函数的名称及参数格式。例如下图:

    图8  Console端的应用子类别(smConsoleServlet)

     其中,smConsoleServlet子类别就必须遵循smConsoleStub类别的接口规定而实作(Implement) onInitialAmount()和onFinished()两个抽象函数。

Slot Machine的框架设计图(方案二)

     在上述方案一里,框架设计师决定了Android游戏端与云层之间沟通讯息的格式,而应用程序开发者只能遵循之而不能制定自己喜欢的讯息格式。如果想让应用程序开发者能自行决定上述的讯息格式,就可更改框架设计如下图:

          图9  Console端游戏框架设计图(依据方案二)

     在此新方案里,smConsoleStub22类别的process()是抽象函数,让应用程序的smConsoleServlet22子类别来实作之。框架里的smConsoleStub22类别只是将讯息转达给应用子类别smConsoleServlet22而已,并不决定讯息格式,也不解析讯息。而是由smConsoleServlet22子类别的process()函数来解析讯息。由于Android游戏端的ac01类别和云层的smConsoleServlet22子类别都属于应用程序,由ac01类别与smConsoleServlet22子类别之间的沟通讯息格式,是应用开发者可以自订了。

3.2  SM游戏的云端框架范例代码

    这云层框架是依据上述的方案一而设计的,其包含两个类别:GM类别和smConsoleStub类别。兹在GAE开发项目里定义上述类别,如下图:

    兹撰写GM.java类别和smConsoleStub.java类别,其代码如下:

   ==> 云端小框架范例代码

    其中,RC类别是依据随机值而换算出获奖的奖项(Rank),其实各家游戏场都有不一样的奖项决定规则,而且随时都可能更换新的奖项规则。上述RC类别只是一个简单范例而已。Android游戏机端传送HTTP讯息给GAE云层,就转而呼叫上述的doGet()函数。此时诞生一个GM对象,并从session取得"gmState"的值,并将此值存入GM对象里,设定了GM对象的状态值。接着,转而呼叫process()函数来解析讯息内容,在依据内容而呼叫GM对象的函数或应用子类别的函数,最后回传讯息给游戏机端。

3.3  SM游戏的应用程序(App)范例代码

  这拉霸机应用程序包括:GAE云层的smConsoleServlet子类别和Android游戏端的ac01子类别。

GAE云层的应用程序代码

   其包括smConsoleServlet子类别。兹在GAE开发项目里定义之,如下:

   兹撰写smConsoleServlet类别,其程序代码如下:

/*---- smConsoleServlet.java ----*/

package misooGAE001;

import com.google.appengine.api.users.User;

import GameFramework.smConsoleStub;

@SuppressWarnings("serial")

public classsmConsoleServletextends smConsoleStub{

      @Override protected int onInitialAmount(User user) {

                    // 读取user's initial(recorded) amount from DB

                    int init_amt = 1000;

                    return init_amt;

             }

       @Override protected boolean onFinished(User user, int final_amount) {

                     // 将user's final amount 存入 DB

                    return true;

 }}

    云层框架smConsoleStub类别并不负责向DB存取游戏玩家的目前余额。所以在游戏启动时,smConsoleStub呼叫应用程序子类别smConsoleServlet的onInitialAmount()函数去DB读取玩家的余额。而且在游戏结束时,框架类别smConsoleStub则呼叫应用程序子类别smConsoleServlet的onFinished()函数去将玩家余额写入DB里,储存起来。

Android游戏端应用程序代码

   应用程序开发者除了上述smConsoleServlet子类别之外,还要开发Android游戏端的应用程序。兹在Android开发项目里定义ac01子类别,如下图:

   兹撰写ac01.java子类别,其程序代码如下:

   ==>  游戏端应用程序代码

    此ac01类别的invokeServlet()函数会启动一个子执行绪(Thread)去发送HTTP讯息给GAE云层的Servlet程序。例如,游戏启动时,立即发送"Init:"讯息给云层,此时画面上出现一个「等待」窗口,如下图:

一直等到接获云层Servlet回复时,就出现测试用的简易游戏画面,如下:

   其中的Amt:1000表示目前此玩家的余额是1000元。此时,玩家可以连续按下按钮来投注更多金额。例如,投注100元的画面如下:

此时,玩家可按下按钮,ac01类别的invokeServlet()函数会启动一个子执行绪(Thread)去发送"Bett:"讯息给云层,此时画面上出现一个「等待」窗口。一直等到接获云层Servlet回复时,就出现更新游戏画面上的金额,如下:

     其中的Rank:3 表示获得第3级别的奖项,押注100元,赢得300元奖金,目前余额为1300元。当玩家按下按钮,ac01类别的invokeServlet()函数会启动一个子执行绪(Thread)去发送"Fini:"讯息给云层,此时画面上出现一个「等待」窗口。一直等到接获云层Servlet回复时,就表示云层已经将余额存入DB,于是ac01就结束了。

3.4  搭配上漂亮的UI画面

  上述的拉霸机范例程序,只要将Android游戏端应用程序更换掉,就可以换上漂亮的操作画面了,如下图所示:

图10  美观的Android拉霸机游戏画面

     然而,由于篇幅的限制,本范例只采用简易操作画面,以便列出完整的程序代码。

4. Android框架 + GAE框架

   Android的跨进程界面IBinder,其角色相当于云层里的Servlet界面:

 图11  Android端框架与GAE云框架的完美整合

     无论Android还是GAE云层都是以框架来支撑「强龙/地头蛇」商业样式。 兹写个GAE框架范例,并进行架构设计如下:

图12  Android框架+ GAE框架的范例

其中的UploadPost和myUploadPost两个类别的程序代码如下:

//UploadPost.java

package com.patrick.ccpmediastore;

import java.io.IOException;

import java.net.URLEncoder;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")

public abstract class UploadPost extends HttpServlet {

 public void doPost(HttpServletRequest req, HttpServletResponse resp)

          throws IOException {

   try { PMF.get().getPersistenceManager().makePersistent(getMediaObject(req));

                      resp.sendRedirect("/");

              } catch (Exception e) {

                      resp.sendRedirect("/?error=" +

                      URLEncoder.encode("Object save failed: " + e.getMessage(), "UTF-8"));

           }}

       protected abstract MediaObject getMediaObject(HttpServletRequest req) ;

}

// myUploadPost.java

package com.patrick.ccpmediastore;

import java.io.IOException;

import java.util.Date;

import java.util.Iterator;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.blobstore.*;

import com.google.appengine.api.users.User;

import com.google.appengine.api.users.UserService;

import com.google.appengine.api.users.UserServiceFactory;


@SuppressWarnings("serial")

public class myUploadPost extends UploadPost {

      private BlobstoreService blobstoreService =

                      BlobstoreServiceFactory.getBlobstoreService();

      protected MediaObject getMediaObject(HttpServletRequest req) {

                      UserService userService = UserServiceFactory.getUserService();

                      User user = userService.getCurrentUser();

                      Map blobs = blobstoreService.getUploadedBlobs(req);

                      Iterator names = blobs.keySet().iterator();

                      String blobName = names.next();

                      BlobKey blobKey = blobs.get(blobName);

                      BlobInfoFactory blobInfoFactory = new BlobInfoFactory();

                      BlobInfo blobInfo = blobInfoFactory.loadBlobInfo(blobKey);

                      String contentType = blobInfo.getContentType();

                      long size = blobInfo.getSize();

                      Date creation = blobInfo.getCreation();

                      String fileName = blobInfo.getFilename();

                      String title = req.getParameter("title");

                      String description = req.getParameter("description") + "from myNewUploadPost";

                      boolean isShared = "public".equalsIgnoreCase(req.getParameter("share"));

               MediaObject mediaObj = new MediaObject(user, blobKey, creation,

                                      contentType, fileName, size, title, description, isShared);

                      return mediaObj ;

 }}

  一样的框架设计思维、方法和技术,应用于Android行动端上,同时应用于GAE云层上。