基于状态机的服务器软件架构

1 简介

随着电子商务的快速发展及计算机网络的迅速普及,各行各业对于高性能服务器的要求越来越高。由此分布式系统,负载均衡,CORBA模型等相关技术应运而生。这些技术着眼于如果将一完整系统的任务分摊到多台计算机上协同处理,以提高系统的吞吐量,但要想系统整体性能有数量级的提升,各单机系统的软件架构设计才是瓶颈。

目前应用服务器已经从单核跨入到多核时代,硬件架构的改变促使软件设计进入到并发多核时代,相应的多进程多线程技术使系统的整体性能得到了大幅度的提升。但目前很多系统在使用多进程多线程技术时,是把进程和功能绑定,线程和用户绑定,由于进程线程上下文的频繁切换,以及系统对于进程和线程数量的约束,导致服务器资源不能得到充分利用。

本文提供了一种基于状态机模型的软件架构及设计方法,详叙了如何将状态机和和软件架构有机集成,最大限度的利用服务器资源,从而大幅度提升性能,最后分析了此架构设计的优缺点。

2 软件架构及模块分析

一个完整的软件系统根据应用不同可以划分为多个软件子系统(例如在通信系统中一般含有操作维护子系统,控制信令子系统,媒体面子系统等),每个软件子系统又由多个进程组成。子系统间和进程间采用各种通用的进程间通信方式来进行通信。服务器的性能主要由处理控制消息的核心进程决定,下图是基于状态机模型的进程逻辑架构图。

 

 

2.1 消息路由模块

在线程和用户绑定的软件架构设计中,当进程接收到用户的初始消息时,进程会创建一个线程,由此线程处理用户的消息,在整个会话过程中,该线程一直处于Active状态,当进程再次接收到用户的消息时,进程会根据用户信息重新把消息转发给该线程进行处理。当会话结束时,线程被释放或者回收到线程池中。

基于状态机模型的软件架构设计中,进程不会为用户创建专门的线程,当进程接收到来自用户的消息时,将控制权转给消息路由模块,消息路由模块发送请求给实例管理模块,要求实例管理模块返回该消息对应的实例号(也可以将实例号传递给各功能处理模块,采用面向对象方法设计时可以采用),一般消息路由模块会将实例号存储为全局数据,然后依次调用各功能处理模块来处理数据。

2.2 实例管理模块

实例管理模块负责实例数据的分配和回收,所有的用户数据,会话信息,中间状态都保存在实例里。

2.2.1 实例管理

实例管理功能主要包括实例区的创建和初始化,实例的分配和回收功能。

实例区的分配和初始化一般在系统上电过程中完成,在系统运行阶段,不进行实际内存的分配和释放。

实例分配和查询策略一般可以根据用户唯一标识的数据结构做优化设计,绝大部分系统通过Hash算法,红黑树算法或者数组链表的方式可以把时间复杂度控制在常量级别。

实例回收只将实例单元清零,然后做逻辑意义上的回收,具体实现时不涉及内存的释放。

2.2.2 用户状态机

一个用户会话从建立到结束,一般要经历多个状态,每个状态可以处理不同的消息,当消息路由模块得到实例当前状态时3 软件架构优缺点分析

,结合收到的消息类型,可以选择对应的功能处理模块(消息路由模块中可以设计一个状态-消息矩阵,矩阵元素为功能处理模块的回调函数)。下图是一个简单的及时短信状态机模型:

2 状态机模型示例

状态模型中各状态之间的变迁是接收消息或者定时器触发。

2.2.3 实例数据单元的设计

实例区由实例数据单元组成,实例数据单元状态又分为ActiveIdle状态,当系统上电加载时,所有的实例数据单元都处于Idle状态,当实例管理模块将某个实例数据单元分配给某个用户会话时,该实例数据单元就处于Active状态,当会话结束时,实例数据单元再回到Idle状态。

实例数据单元是用户状态机的容器,当然为了支持功能处理模块的运行,实例数据单元中还包含用户ID,会话ID,会话相关信息等内容。

以下是一个简单的实例数据结构定义:

struct tagUserEntity

{

int iUserId;            //用户ID

int iSessionId;          //会话ID

int iUserStatus;         //用户状态

int iAge;              //用户年龄

}

其中iUserStatus字段用来标识用户状态,不同的值可以表示不同状态。

在定义实例数据单元的时候,为了提高内存读取性能和减少实例数据单元占用空间,需要考虑字节对齐的因素。

因为实例数据单元只是保存中间状态信息和用户会

因为实例数据单元只是保存中间状态信息和用户会话信息,假设实例数据单元由100integer字段构成,每个实例数据单元占用400Byte内存,1MB(1024*1024Byte)内存可以容纳2621个实例数据单元,也就是说1MB内存可以容纳2621个并发会话。

2.3 功能处理模块

消息路由模块收到实例管理模块的应答以后,根据实例数据区的状态和消息类型查询状态-消息矩阵,得到具体的功能处理模块的回调函数,然后将消息发送给功能处理模块。

功能处理模块根据消息内容和实例数据单元的状态信息,处理该消息,保存中间状态信息到实例数据单元中,发送消息到进程外或设置定时器,消息处理结束返回。

功能处理模块设计时为提高系统性能可以考虑根据不同功能创建不同的专用线程。对于功能相对简单的系统而言,如果采用本文基于状态机的模型,一般系统瓶颈不会出现在功能处理中,为降低系统设计的复杂性,不建议采用多线程技术。

2.4 公共处理模块

为提高系统的可维护性和代码的可重用性,一般都设计一个公共处理模块。该模块主要包含公用函数,消息发送函数,定时器处理函数,非功能性关键算法等内容。此模块和状态机模型关系不大,本文不深入讨论。

 

3.1 高性能

3.1.1 内存操作少

由于实例区在系统上电阶段就已分配好,在用户创建会话和结束会话的时候,没有实际的内存分配和释放,消除了其他系统架构中频繁的内存分配和释放所带来的时间消耗和内存碎片回收。

3.1.2 无线程上下文切换

用户状态数据和会话信息全部保存在实例数据单元中,不需要为每个用户创建独立的线程,自然也就消除了线程上下文切换所带来的时间成本。

3.1.3 无数据拷贝

各模块间只传递实例数据单元的引用和消息的引用,不存在数据拷贝动作,减少了额外的内存的复制拷贝操作。

3.2 大容量

一般操作系统都有内存和内存中线程数量的限制,为了采用原先的设计架构(用户和进程绑定或用户和线程绑定),很多公司都在操作系统以上再封装一层私有的平台

,借此来支持各种应用中大容量的需求。

前文已经提到,,假设实例数据单元由100integer字段构成,1MB内存可以容纳2621个并发会话。

100MB内存就能支持262100个并发会话。在很多系统中实例数据单元一般只由10~30integer字段构成,单台服务器内存一般都超过1G以上。所以采用该模型可以很容易的支持100万以上用户。

3.3 系统瓶颈

根据以上分析可以发现,基于该模型的系统对于内存和CPU的使用都不高,系统的吞吐量很大程度上受限于网络处理能力,建议可以采用如下两种方式避免系统瓶颈:

3.3.1 分离控制子系统和媒体子系统

控制子系统一般控制系统接入功能,其所处理的消息一般内容少,占用网络带宽少,但消息交互很多。媒体子系统一般负责媒体的发送和接收,占用很大的网络带宽。一般策略是分离控制子系统和媒体子系统,在控制子系统中采用该状态机模型。

3.3.2 增加负载均衡机制

考虑到网络容量的限制,可以采取增加多台控制服务器,服务器之间采用负载均衡算法提高系统的吞吐量,同时也能提高整个系统的鲁棒性。

 

你可能感兴趣的:(基于状态机的服务器软件架构)