Android和Docker的一致架构设计(3)
-- 建立企业主的<核心集装箱>
By 高焕堂 (台灣Docker論壇 主席)
misoo.tw@qq.com 2015/03/05
高煥堂的相關文章:
A01、从「集装箱」思考Docker风潮:Docker潮流下的赢家策略
A02、Docker:从容<器>到集装箱设计之<道>
B01、Android和Docker的一致架构设计(1):追求今天决策的未来性
B02、Android和Docker的一致架构设计(2):包装<跨集装箱>的通信协议
B04、Android和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、设计一个业务层的<核心集装箱>
兹回忆一下之前的文章:《Android和Docker的一致架构设计:追求今天决策的未来性》,曾经说明了如何降低集装箱之间的相依性,以便创造出具有高度弹性的系统架构。例如下图:
图-8、如何提升A的复用性?
其中的A集装箱与B1、B2、B3等诸多集装箱之间,可做动态性的组合。
图-9、与B的动态组合可提升A的复用性
我在之前的文章:《Android和Docker的一致架构设计:追求今天决策的未来性》里,借镜于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 ~