02 项目架构-IPC通信框架

02 项目架构-IPC通信框架,第1张

Android App开发中的IPC(进程间通信)无处不在。比如我们使用的 AlarmManager 、 InputMethodService 都是系统为我们提供的服务,处于单独的进程中。如果需要在自己的App进程中使用这些服务就需要进行IPC通信。

除此之外,我们自己的程序中也会存在进程通信的可能(特别是在一些大型APP中)

QQ:未登陆

微信:使用一段时间后:

场景:在Service中开启定位服务,Service处于单独的进程,需要在App主进程或者其他APP中获得定位结果。

服务中提供暴露给其他进程使用的方法并提供一个 ServiceId 注解标记,而服务实现中必须给到相同的 ServiceId 与方法实现,不强制要求 LocationManager 一定需要继承 ILocationManager j接口,但是为了保证方法签名统一建议继承。(不然一个是getLocation,另一个是getLocation2就不好玩了)

在Service进行定位,定位结果在 LocationManager 中记录。在这个Service中使用框架注册 LocationManager 。

不需要返回 Binder 对象,这意味着使用者不需要编写繁琐没任何提示的AIDL文件。

框架内部会提供 comenjoyipcIPCService$IPCServiceX 多个预留Service,用于与其他进程通信,如果一个App存在多个进程都需要提供各自进程的服务,可以使用不同的Service。所以本质上依然是借助的Service+Binder通信,但框架将细节封装隐藏,使用更加简单。

获得结果对象后就能像调用本地方法一样调用远程方法(RPC调用)。

在使用中简化了:

1、不需要自己定义AIDL接口,使用的JavaBean也不要求实现 Parcelable 接口;

2、在客户端不需要直接使用 bindService 获得 Binder 对象;

服务端需要定义暴露服务的接口(ILocationManager),客户端如果是其他APP,则需要将接口类放到自己的源码中(不需要接口实现)。接口中定义的方法就是服务端提供给其他进程使用的方法。

整个框架包含了服务端与客户端两端接口。

在服务进程中会缓存 ServiceId 与对应的服务实现Class对象: 服务表 ,同时服务实现中的所有方法列表也需要进行记录: 方法表 。由于一个服务中可能存在多个方法,所以其数据结构为 Map<Class,Map<String,Method>> ,外层 Map 的key为服务Class,内层 Map 的key则为方法标记。

当客户端需要调用服务时,将 ServiceId 、MethodName以及执行方法需要的参数传递给服务端,服务端查表利用反射 Method#invoke 即可执行服务中的方法。

其中客户端的请求被封装为 Request 对象,服务端响应则封装为 Response 对象

服务端只需要暴露服务接口给其他进程使用,所以服务端只需要调用框架的注册接口 regiest 对服务实现进行注册。( 注册的是服务实现,而不是服务接口 )

注册时,通过反射获得Class上的 ServiceId 即可记录 服务表 。同时利用反射获得Class中所有的public Method即可记录 方法表

由于框架本质还是利用Binder来完成通信,为了与其他进程通信,框架内部提供了多个预留的Service。

通信Service会返回一个AIDL生成的Binder类对象

客户端使用 send 方法向服务端发起请求。

服务端接收到请求后的实现:

客户端需要先与服务端建立连接,因此框架中提供了 connect 方法,内部封装 bindService 实现与服务端通信Service( IPCService )的绑定。

唯一需要注意的是:

当完成绑定后,客户端就可以获得服务端通信Service提供的 IIPCService 对象,客户端调用 IIPCService#send 发起请求。

当我们需要获得 Location 。则应该调用 LocationManagergetDefault()getLocation() 。这句调用会需要执行 LocationManager 的两个方法: getDefault 与 getLocation 。

然而这个对象存在服务端,客户端如何获得?

我们可以利用动态代理,在客户端创建一个 "假的" 服务接口对象(代理)。

当我们执行这个代理对象的方法( getLocation )时,会回调 IPCInvocationHandler#invoke 方法,在这个方法中框架会向服务端发起请求: IIPCService#send

而 getLocation 会返回一个 Location 记录定位信息的对象,这个对象会被服务端json序列化发送过来,因此,客户端只需要在此处获得 Method 的返回类型并反序列化即可。

RPC指的是:从客户端上通过参数传递的方式调用服务器上的一个函数并得到返回的结果,隐藏底层的通讯细节。在使用形式上像调用本地函数一样去调用远程的函数。

比如我们使用Ok>服务器是用来处理高并发的请求,同时能够满足扩展的业务逻辑的需求,最重要的是满足三点:并发性,稳定性,扩展性。
经历过两款上线游戏产品,见识到了游戏行业的杂乱无章,虽然和传统软件行业相比,少了那么些规范,但是对个人能力要求还真不比传统软件行业低。
今天开始,陆续利用业余时间将自己设计的一个服务器的框架贴出来,也会包好一些基本的代码,也会用到一些开源库。从最基础的讲起,首先看看一个实时网络游戏服务器的框架:

目前市面上的游戏,总的来说分为两类:
1弱联网类游戏,像手机上的卡牌类游戏(MT,Dota传奇等),大部分逻辑在客户端处理,不需要实时联网,这类游戏只有一个玩家,而且只有PVE模式,就是打游戏中的机器人(AI),不存在玩家与玩家的实时交互。例如一场副本打斗,只有在开始和结束,才会连接服务器,请求获取或者存储数据,打斗过程由客户端计算完成,最后将战斗结果提交服务器就行了。

2强联网类游戏,典型的就是MMORPG或者MMARPG的类型的游戏,一般常见于端游或者页游,也包含手游。在一个地图中,同时有很多玩家,任何一个玩家的状态或者属性发生变化,服务器就需要实时更新游戏中角色的状态,并且通知到周围的玩家。例如在副本中,一个玩家释放技能,攻击范围,伤害计算这些逻辑都是服务器来完成的,而客户端只需要负责特效的显示,这个过程中需要实时的数据交互。
显然,第2种,MMORPG类游戏需要服务器做更多的事情,对服务器的运算要求更高,实时性要求更高,自然实现起来更复杂。

一个大型的网落游戏服务器应该包含几个模块:网络通讯,业务逻辑,数据存储,守护监控(不是必须),其中业务逻辑可能根据具体需要,又划分为好几个子模块。
这里说的模块可以指一个进程,或者一个线程方式存在,本质上就是一些类的封装。

对于服务器的并发性,要么采用单进程多线程,要么采用多进程单线程的方式,说说两种方式的优缺点:

一、单进程多线程的服务器设计模式,只有一个进程,但一个进程包好多个线程:
网络通讯层,业务逻辑,数据存储,分别在独立的线程中,无守护进程。
优点:
1数据共享和交换方便,使用全局变量或者单例就可以,数据存储方便。
2单进程,服务器框架结构相对简单,编码容易。
缺点:
1所有功能只能在单个物理服务器上,不能做成分布式。
2不方便监控各个线程状态,容易死锁
3一个线程出错,例如内存非法访问,栈空间被破坏,那么服务器进程就退出,所有玩家掉线,影响大。

二、多进程单线程的服务器设计模式,多个进程,每个进程只有一个线程:
网路通讯,业务逻辑,数据存储,守护进程,分别在不同的进程。
优点:
1各个进程可以分布在不同的物理服务器上,可以做成分布式的服务器框架,例如可以将数据存储单独放到一个物理服务器上,供几个区的服务器使用。将网络通讯进程独立出来,甚至可以做成导向服务器,实现跨服战。
2可以通过守护进程监控其它进程状态,例如有进程死掉,马上重启该进程,或者某个进程cpu使用率接近100%(基本可以判断是某个逻辑死循环了), 强制kill掉该进程,然后重启。
3单个服务器进程异常退出,只要不是网络通讯进程(一般这个都会比较稳定,没什么逻辑),那么就可以及时被守护进程重启,不会造成玩家掉线,只会造成在1-2秒内,某个逻辑功能无法使用,甚至玩家都感觉不到。
4服务器通过共享内存进行数据交换,那么如果其中一个服务器死掉,数据还在,可以保护用户数据(当然多线程也可以使用共享内存)。
5并发性相对多线程要高点。
缺点:
1不方便使用互斥锁,因为进程切换的时间片远远于线程切换,对于一个高并发服务器是无法允许这么高时间片的切换代价的。因此必须设计好服务器的框架,尽量避开使用锁机制,但要保证数据不出错。
2多进程编程,在各个进程间会有很多通讯,跨服务器进程的异步消息较多,会让服务器的编码难度加大。

Nodejs 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。 Nodejs 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Nodejs 的包管理器 npm,是全球最大的开源库生态系统。(nodejs官网上的介绍),正如官网上介绍的那样,nodejs确实很牛!怎么个牛法?看看下面的代码就知道了。

//引入>

从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构(SMP:SymmetricMulti-Processor),非一致存储访问结构(NUMA:Non-UniformMemoryAccess),以及海量并行处理结构(MPP:MassiveParallelProcessing)。

一、SMP(SymmetricMulti-Processor)

所谓对称多处理器结构,是指服务器中多个CPU对称工作,无主次或从属关系。各CPU共享相同的物理内存,每个CPU访问内存中的任何地址所需时间是相同的,因此SMP也被称为一致存储器访问结构(UMA:UniformMemoryAccess)。对SMP服务器进行扩展的方式包括增加内存、使用更快的CPU、增加CPU、扩充I/O(槽口数与总线数)以及添加更多的外部设备(通常是磁盘存储)。

SMP服务器的主要特征是共享,系统中所有资源(CPU、内存、I/O等)都是共享的。也正是由于这种特征,导致了SMP服务器的主要问题,那就是它的扩展能力非常有限。对于SMP服务器而言,每一个共享的环节都可能造成SMP服务器扩展时的瓶颈,而最受限制的则是内存。由于每个CPU必须通过相同的内存总线访问相同的内存资源,因此随着CPU数量的增加,内存访问冲突将迅速增加,最终会造成CPU资源的浪费,使CPU性能的有效性大大降低。实验证明,SMP服务器CPU利用率最好的情况是2至4个CPU。

二、NUMA(Non-UniformMemoryAccess)

由于SMP在扩展能力上的限制,人们开始探究如何进行有效地扩展从而构建大型系统的技术,NUMA就是这种努力下的结果之一。利用NUMA技术,可以把几十个CPU(甚至上百个CPU)组合在一个服务器内。

NUMA服务器的基本特征是具有多个CPU模块,每个CPU模块由多个CPU(如4个)组成,并且具有独立的本地内存、I/O槽口等。由于其节点之间可以通过互联模块(如称为CrossbarSwitch)进行连接和信息交互,因此每个CPU可以访问整个系统的内存(这是NUMA系统与MPP系统的重要差别)。显然,访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问NUMA的由来。由于这个特点,为了更好地发挥系统性能,开发应用程序时需要尽量减少不同CPU模块之间的信息交互。利用NUMA技术,可以较好地解决原来SMP系统的扩展问题,在一个物理服务器内可以支持上百个CPU。比较典型的NUMA服务器的例子包括HP的Superdome、SUN15K、IBMp690等。

但NUMA技术同样有一定缺陷,由于访问远地内存的延时远远超过本地内存,因此当CPU数量增加时,系统性能无法线性增加。如HP公司发布Superdome服务器时,曾公布了它与HP其它UNIX服务器的相对性能值,结果发现,64路CPU的Superdome(NUMA结构)的相对性能值是20,而8路N4000(共享的SMP结构)的相对性能值是63。从这个结果可以看到,8倍数量的CPU换来的只是3倍性能的提升。

三、MPP(MassiveParallelProcessing)

和NUMA不同,MPP提供了另外一种进行系统扩展的方式,它由多个SMP服务器通过一定的节点互联网络进行连接,协同工作,完成相同的任务,从用户的角度来看是一个服务器系统。其基本特征是由多个SMP服务器(每个SMP服务器称节点)通过节点互联网络连接而成,每个节点只访问自己的本地资源(内存、存储等),是一种完全无共享(ShareNothing)结构,因而扩展能力最好,理论上其扩展无限制,目前的技术可实现512个节点互联,数千个CPU。目前业界对节点互联网络暂无标准,如NCR的Bynet,IBM的SPSwitch,它们都采用了不同的内部实现机制。但节点互联网仅供MPP服务器内部使用,对用户而言是透明的。

在MPP系统中,每个SMP节点也可以运行自己的 *** 作系统、数据库等。但和NUMA不同的是,它不存在异地内存访问的问题。换言之,每个节点内的CPU不能访问另一个节点的内存。节点之间的信息交互是通过节点互联网络实现的,这个过程一般称为数据重分配(DataRedistribution)。

但是MPP服务器需要一种复杂的机制来调度和平衡各个节点的负载和并行处理过程。目前一些基于MPP技术的服务器往往通过系统级软件(如数据库)来屏蔽这种复杂性。举例来说,NCR的Teradata就是基于MPP技术的一个关系数据库软件,基于此数据库来开发应用时,不管后台服务器由多少个节点组成,开发人员所面对的都是同一个数据库系统,而不需要考虑如何调度其中某几个节点的负载。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/10343412.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-07
下一篇 2023-05-07

发表评论

登录后才能评论

评论列表(0条)

保存