AndroidDocker一致架构设计(3)

-- 建立企业主的<核心集装箱>

 

By 高焕堂 (台灣Docker論壇 主席)

misoo.tw@qq.com  2015/03/05 

 

高煥堂的相關文章:

A01从「集装箱」思考Docker风潮:Docker潮流下的赢家策略

A02Docker:从容<器>到集装箱设计之<道>

B01、Android和Docker的一致架构设计(1):追求今天决策的未来性

B02、Android和Docker的一致架构设计(2):包装<跨集装箱>的通信协议

B04Android和Docker的一致架构设计(4):<分合自如>的设计模式

 

前言

   在需求、数据和软件愈来愈碎片化的趋势下,集装箱包装了微服务(Micro-Service)成为主流。这些微服务在运行时间(Run-time)经常需要动态性组合成为各式各样的App来支撑企业多变的业务流程(Business Process)。在本文里,就拿上述的<动态组合>需求为例,展示企业App层的<核心集装箱>的角色。为了促进<端与云>架构设计概念的一致性,本文一方面借镜于Android核心进程(即SystemServe)的设计和角色;一方面运用Docker核心引擎来妥善管理集装箱,可迅速替各企业主设计出其独特的核心集装箱,来协助创造业务App模块之间,极为迅速的、瞬间的动态组合,来支撑企业流程和活动。     

  • Android终端的服务厂商,很可能成为Docker平台最活耀的业主。 
  • Android终端用户群,将成为Docker集装箱服务的主要消费群体。          

  其实,业务App模块之间的动态组合,只是本文的应用范例而已。本文的真正目的是要藉这应用范例来呈现出企业主<核心集装箱>的角色和意义。并将Android平台的核心进程概念引用进来,落实为Docker集装箱,也创造<Android + Docker>一致性的端&云整合架构,迈向一致、整体的美感。

 

1、简介<动态组合>范例

  Linux操作系统有它的核心,称为Linux Kernel。而Docker也有它的核心,称为Docker Engine。

   

    图-1、两个基础平台的核心

同样地,在云平台上,各企业主也都可以建立一个自己的核心集装箱。

   

    图-2、替企业建立它的核心集装箱 

  于是,形成一个<三合一>的企业云平台核心。

          

     图-3、形成三合一的核心架构

  其中,Docker核心的主要任务是管理集装箱的布署信息。例如,掌握集装箱(镜像)之间的相依性(Dependency)。而企业云核心则掌管集装箱内部App的业务模块(Business Module)之间的相依性,以及它们之间的动态性组合(Dynamic Composition)。 举例来说,在游戏业务里,有三个业务模块,分别由不同团队开发,并各自打包成为Docker集装箱,如下图:

  

   图-4、一款游戏里的三个模块

  在这个游戏里,玩家操控飞机,携带摄像头(Camera)飞越港口上空,游戏画面会出现摄像头所拍到的景象。如下图:

  

   图-5、飞到港口上空所拍摄到的景象

  这三项企业模块之间的动态結合(Association)关系,并非布署时间(Deployment-time)的相依性,並不属于Docker核心所掌管的范围。当飞机继续飞到另一个城市上空时,将会动态結合另一个集装箱的<城市3D图>,飞机所携带的摄像头会拍到新的城市景象。如下图:

  

  

   图-6、飞到另一个城市上空所拍摄到的景象

  顾名思义,”Docker”是:码头上的集装箱搬运工。Docker不仅要把集装箱搬运到不同平台(码头),还要搭配业务模块的动态组合而及时建立或删除集装箱,例如,飞机即将到某个城市上空之前,必须及时将<城市3D图>集装箱建立起来;此外,也可能立即将<港口3D图>集装箱删除了。这意味着,在App执行期间(Run-time)可能些业务模块必须及时将最新状态通知Docker Engine核心。但是,又不能让各业务模块开发者都随意使用Docker Engine的API,以避免各业务模块里处处充斥着Docker API,业务模块才不会被Docker绑住。

 于是,有必要特别建立一个业务模块的核心集装箱,内含各种管理型的软件模块,如App Manager模块。

                 

       图-7、业务层的核心集装箱

   由它来统一负责与Docker核心的通信与合作。

 

2设计一个业务层的<核心集装箱>

   兹回忆一下之前的文章:AndroidDocker的一致架构设计:追求今天决策的未来性》,曾经说明了如何降低集装箱之间的相依性,以便创造出具有高度弹性的系统架构。例如下图:

   

    图-8、如何提升A的复用性?

  其中的A集装箱与B1、B2、B3等诸多集装箱之间,可做动态性的组合。

   

    图-9、与B的动态组合可提升A的复用性

  我在之前的文章:AndroidDocker的一致架构设计:追求今天决策的未来性》里,借镜于Android而设计一个具有高度未来性的设计,如下图:

   

    图-10、动态组合的基本架构

  基于这项设计,能创造各式各样的组合关系。如下图:

   

    图-11、动态创建形形色色的组合

   在动态组合的情境中,这些B1、B2、B3等诸多集装箱,并非事先就全部创建完成的,而是需要使用到的时候,才动态创建的。如下图,在Run-time动态创建了B集装箱,然后才能去调用它内部的App服务。

   

    图-12、也常动态创建Docker集装箱

  在C集装箱里的绿色模块担任了<组合者>的角色,是合理的。但是它必须情求Docker Engine来协助创建(Build)这个B集装箱。然后才能去调用B集装箱内部的App服务。如下图:

   

    图-13、绿色模块(组合者)请求Docker Engine来协助

  表面上看来,上图里的设计是合理的。其实不然,因为Docker Engine的API相关代码会被写在C集装箱的绿色模块里。导致C集装箱与Docker Engine的API产生高度的相依性(Dependency),不是一项有效的(有未来性的)美好设计!!

   

    图-14、但却让绿色模块高度依赖于Docker Engine的API

  此时,就设计出一个新的<核心集装箱>,让它负责与Docker Engine通信的任务,有效降低其他各集装箱与Docker Engine的相依性,大幅提升企业软件的跨平台(如Docker平台)的能力。如下图:

   

    图-15、将Docker Engine的API相关代码移出绿色模块

  这种效益,随着企业的业务模块的成长,会显得更加亮丽。例如下图里,C集装箱内部的业务模块成长了、变复杂了,却完全被Docker集装箱隔离了,让C集装箱具有极高度的跨平台性,更加实践了Docker集装箱的无限美意。不仅仅跨越OS操作系统平台,更能跨越Docker集装箱平台。

   

   图-16、各个组合模块皆独立于Docker Engine的API

   一般而言,企业主在云平台上,只需要一个像M这种<核心集装箱>,所以当Docker Engine的API改版了,或Docker Engine被抽换掉了,只需要改写M集装箱里的红色模块而已,非常简单容易。这个M就通称为企业主的<核心集装箱>。

 

3、观摩Android架构设计

3.1 基本架构

 Linux平台有其核心(即Linux Kernel),而Docker平台也有其核心(即Docker Engine)。一样地,Android平台也有其核心(即SystemServer)。在本节哩,就来借镜于Android核心进程(Process),来引导我们思考如何设计各企业主的云平台核心集装箱。在Android平台哩,SystemServer进程就扮演着Android平台的核心角色。如下图:

   

     图-17、SystemServer是Android的核心进程

  在Android里,Activity主要是提供UI画面的布局(Layout)与设计,来支持人机交互的需要。在这人机交互的过程中,Activity常常需要动态地去启动一个或多个Service。而Service主要是提供幕后的服务,例如播放游戏的背景音乐、从网络上下载图片或影片等任务。这些Activity和Service经常是由不同的团队分别开发、并在不同的进程里执行。举例来说,在Run-time期间,当上图里myActivity需要去调用myService的服务时,就会发出bindService信息给SystemServer进程里的AMS(即ActivityManagerService)模块,目的是去绑定myService。此时,这个myService的进程可能尚未创建,也可能已经存在了。如果,还不存在的话,AMS就会透过Zygote进程来发出信息给Linux Kernel,要求创建一个新进程。如下图:

   

    图-18、AMS发出信息给Linux Kernel

Linux Kernel接到AMS的请求之后,它就去创建一个新的进程,并且把Service的代码载入(Load)到这个新进程里。如下图:

    

     图-19、Linux Kernel创建一个新进程

  然后,AMS就要求Service进程的myService模块,回传IBinder接口。这过程就通称为:绑定(Bind)了myService。其目的是要建立myActivity与myService之间的联结(Connection)。AMS取得myService的IBinder接口之后,就回调(Callback)这myActivity模块,把IBinder接口递交给myActivity,就完成了绑定myService的任务了。也就是myActivity成功地建立了与myService之间的联结了。

    

     图-20、myActivity绑定了myService

  一旦取得myService的IBinder接口,myActivity就能透过IBinder接口而调用myService的服务了。如下图:

    

      图-21、myActivity调用myService的服务

   关于App软件的进程都是由AMS来掌握,如果需要新的进程,AMS就会透过Zygote要求Linux Kernel来创建。所以SystemServer就扮演Android平台的核心角色,而AMS是SystemServer进程里的重要模块之一。我们可以看到,无论是Docker或Android平台架构里都有其核心进程(集装箱)。在云平台上,可借镜于Android架构,也设计出企业主的核心进程(集装箱),将是非常有帮助的。

 

3.2 Android的应用程序代码

  兹建立Android开发项目:

  

  此程序的执行画面如下:

  

  其源代码如下: 

// myActivity.java
package com.misoo.pk01;

public class myActivity extends Activity implements OnClickListener {
   //………
   public void onCreate(Bundle icicle) {
     //………
     bindService(new Intent("com.misoo.pk01.REMOTE_SERVICE"), mConnection, Context.BIND_AUTO_CREATE);
   }
   private ServiceConnection mConnection = new ServiceConnection() {
     @Override

     public void onServiceConnected(ComponentName className, IBinder ibinder) {
       ib = ibinder;
     }

   };
   public void onClick(View v) {
      // ………
      ib.transact(1, data, reply, 0);
      // ………
}}

// myService.java
public class myService extends Service implements Runnable {
   // ……….
   @Override public void onCreate() {
      super.onCreate();
      mBinder = new myBinder();
    }
   @Override public IBinder onBind(Intent intent) {
      return mBinder;
}}

// myBinder.java
package com.misoo.pk01;
// ........
public class myBinder extends Binder{
   @Override
   public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
   // …….
   myService.h.sendMessage(msg);
   // ……
}}

  兹说明上述的软件程序,首先看代码:

   bindService(new Intent("com.misoo.pk01.REMOTE_SERVICE"), mConnection, Context.BIND_AUTO_CREATE);

   就是myActivity调用父类Activity的bindService()函数,然后由Activity转而调用AMS的bindService()函数。这就是上图-18所呈现的情形。接着,如果需要创建新的进程,AMS就会透过Zygote要求Linux Kernel创建一个新进程。这就是上图-19所呈现的情形。 此时,有了myService进程,AMS就访问这个myService进程,转而执行到myService模块的onCraete()和onBind()函数,这段代码就是:

   @Override public void onCreate() {
      mBinder = new myBinder();
    }
   @Override public IBinder onBind(Intent intent) {
      return mBinder;
    }

其中,onBind()函数就将IBinder接口回给AMS。接着AMS就回调(Callback)到myActivity模块,把IBinder接口地交给myActivity。这项回调的代码如下:

   private ServiceConnection mConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName className, IBinder ibinder) {
         ib = ibinder;
    }};

这就是上图-20所呈现的情形。此时,myActivity就接到IBinder接口了。myActivity就可调用 IBinder接口的transact()函数。这段代码如下:

   public void onClick(View v) {
      // ………
      ib.transact(1, data, reply, 0);
      // ………
    }

现在执行到IBinder接口的transact()函数,其转而调用myBinder的onTransact()函数,就执行到代码如下:
   public class myBinder extends Binder{

     @Override 
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
       // …….
       myService.h.sendMessage(msg);
       // ……
   }}

 = => 请看完整源代码

   这onTransact()就转而调用myService的服务了。当您仔细观察上述Android应用程序代码,统统看不见与AMS、Zygote及Linux Kernel的API接口的相关代码。都是集中并封装于SystemServer的核心集装箱(进程)里。这种优越的架构设计替企业主带来了极大的利益,不仅仅让企业的App模块可以跨OS平台,也能更进一步跨Docker平台。 

 

4、一致的架构:应用于Docker集装箱

   无论是Linux、Docker或Android都各有它自己的核心部分。而且Docker和Android都以进程为沙盒空间(也可称为集装箱),例如,Docker的核心进程是Docker Daemon;而Android的核心进程是SystemServer。基于一致性的架构设计,我们也可以替企业主,在其云平台上建立一个业务层软件的核心。

   从上述Android的平台架构,可以看出来,其核心进程(SystemServer)是蛮复杂的,而且还包装了与底层平台(如Linux Kernel)的API相关代码,所以核心进程的内部(代码)是既复杂又多变。也就是因为它的复杂多变,更需要集装箱的<序中有乱>的特质来将它的复杂多变包装起来,呈现出简单的次序。因之,Docker集装箱恰可派上用场,它让我们更能做出好的系统核心,包括企业云平台的核心集装箱。首先从Docker核心(Docker Engine)谈起,如下图:

  

   图-22、提供三个镜像给Docker Engine

  Docker Engine依据M的镜像(Image)来创建一个M集装箱。它就扮演核心集装箱的角色。如下图:

  

   图-23、创建了核心集装箱(M)

  在核心集装箱(M)里,可以添加一些<管理型>的软件模块,例如上图里的App Manager模块,担任插件(Plug-in)的管理等任务。此外,未来也可以逐渐添加更多的插件模块。至于这M核心集装箱里该摆入那些模块,就是我们的职责了,就是:依据平台、技术、业务等不同需求或条件限制来考虑,然后设计出来的。通常这种核心进程都是常驻型的集装箱,也算是守护进程(Daemon)。接下来,企业主还可以透过Docker Engine来创建其他的集装箱来跑各式各样的业务App模块。例如,基于A的镜像来建立A集装箱,如下图:

  

   图-24、创建一个A集装箱

  在A集装箱的执行时(Run-time),如果需要动态性去绑定S集装箱里的myServive服务,就可发送信息给M(核心)集装箱里的App Manager模块,由它来请求Docker Engine创建一个S集装箱,如下图:

 

       图-25、A委托M来创建S集装箱

  然后,App Manager就去绑定S集装箱里的myService服务,并取得myService的接口。这过程就通称为:绑定了myService,如下图。其目的是要让A集装箱建立起与S集装箱的连结联机(Connection),也就是支持两个集装箱之间的动态组合。

 

       图-26、A委托M来绑定Service模块

  App Manager取得myService的接口之后,就回调这绿色模块,把myService的接口递交给绿色模块,就完成了绑定myService的任务了。一旦取得myService的接口,myActivity就能透过该接口而调用myService的服务了。如下图:

 

       图-27、A与S交换信息或数据

   从这图里可看到,目前已经成功建立了A集装箱的绿色模块与S集装箱的myService之间的动态组合。在执行过程中,可能需要再创造一个动态组合,例如在下图里,黄色模块想与Service-x模块进行动态组合。此时,就可透过M核心集装箱里的App Manager来绑定Service-x模块,如下图:

 

       图-28、A和S集装箱内部随着企业需求而变化

   App Manager取得Service-x接口之后,就回调这黄色模块,把Service-x的接口递交给黄色模块,就完成了绑定Service-x的任务了。一旦取得Service-x的接口,黄色模块就能透过该接口而调用Service-x的服务了。如下图:

 

        图-29、随着企业需求变化而动态组合

   其中,Docker Engine负责管理集装箱本身,而企业主的M核心集装箱则负责建立业务模块之间的关系;两者相辅相成,创造云平台的美好次序(Order)。

 

5、结语

5.1  <命令流与数据流的分离法则

    软件有两种主要流程:一种是命令流(Command flow),而另一种是数据流(Data flow)。许多软件人员常常误认为数据流才是最重要的。其实不然,在架构设计上,命令流程的设计更是关键。在我写的《思考软件、创新设计:A段架构师的思考技术》一书的第10章哩,也提出了云平台架构设计的十项法则,其中的第4项就是:命令流与数据流的分离法则。例如,下图里的软件交互调用流程就是命令流。

 

            图-30、命令流之范例

  这图里展现了命令流,它的目的是去建立绿色模块与Service-x之间的连结(Connection) 。以日常生活来比喻,这命令流是用来指挥各模块(如Docker Engine、App Manager)去合作搭建桥梁;而数据流则是让行人或车辆从搭好的桥梁上鱼贯地通过。例如下图:

 

            图-31、数据流之范例

  一般而言,命令流的软件逻辑常常比数据流要复杂许多,所以藉由Docker集装箱来将复杂多变包装起来,发挥了集装箱天赋的<序中有乱>特质,让我在设计企业主的<核心集装箱>时,变得简单许多了。此外,善加利用<命令流与数据流的分离>法则,可带来系统的弹性,并大幅提升数据流的执行效能。例如,控制手机摄像头(Camera)的信息是命令流;而传送预览(Preview)视像的是数据流。命令流指挥数据流,通常可以透过命令流API去通知两端的模块,动态启动两端插件,透过动态API(如Config文件提供)串接两端;静态Command API支撑动态Data API。

       例如,在建置大数据平台时采用Hbase。这Hbase是以Java开发的,其提供了Java基础接口,在C层则使用thrift机制。如果咱们的C层模块想存取Hbase时,使用的技术是:C调用jni,再调用Hbase的Java接口(亦即C->jni->Java),但这种架构方式,会导致性能下降,该如何解决这个议题呢?  这个议题可以将两种流程分离开来。并且厘清数据来原(Data Source)是在C层(可能是先已经从Hbase查询出来,放在C层了)或是在Java层的Hbase里。如果数据来源是在Hbase里,其thrift接口就是合适的数据来源接口。如此,数据来源接口在C层,而Client(即数据目的地)也在C层。于是:

  • Data flow是C->C,不要经由Java层,效率就极高。
  • Command flow可以经过Java层,有助于控制点放在Java层。
  • Command flow:C->JNI->Java接口,要求Java模块把Hbase端的C接口传递给Client。这常常在编译时(At Compile-time)就已经安排的API,通称为静态配置的。此时,Java模块可动态读取Config配置文件,依其指示而把Hbase端的特定C接口(如thrift API)传递给Client,这通称为动态配置的。
  • Data flow: Client就可调用上述动态配置的Hbase的C接口(若跨进程,可透过IBinder接口),进而顺利取得所需的数据。 

5.2  带给企业无限活力

  每一台电脑都有<主板>(简称Mother-Board),它是IT产业人人都接触过的东西。它是一个集装箱,包容了极多样化的电脑芯片及相关模块。后来,人们就将全世界的电脑主板都连接起来,就成为当今风行的互联网(Internet)了。前面也提过了,在当今的需求和软件碎片化的大数据时代里,一个企业主的业务层App软件常常分布在各式各样不同的工有云或私有云上,我们可以协助众多企业主建立云平台上的核心集装箱,它就像一个电脑的主板,其控制着周边的相关模块。然后,再将这些分布于各云端的核心集装箱连结起来,成为企业主专属的大数据平台。

  基于Docker集装箱的<序中有乱>特质,虽然核心集装箱内部的软件逻辑是复杂多变的,但集装箱的概念和操作接口是很简单的。也因为这项简单之美,带给它无限的生命力,也带给众多企业无限的活力。  

 

高煥堂的相關文章: 

 A01、从「集装箱」思考Docker风潮:Docker潮流下的赢家策略 

 A02、Docker:从容<器>到集装箱设计之<道>

 B01、Android和Docker的一致架构设计(1):追求今天决策的未来性

 B02、Android和Docker的一致架构设计(2):包装<跨集装箱>的通信协议

 B04、Android和Docker的一致架构设计(4):<分合自如>的设计模式

 ~ End ~