简单介绍多进程和多线程游戏服务器

首先贴下多进程单线程和单进程多线程的特点:

  多进程:有独立的地址空间,进程之间不共享内存和变量,但可以通过共享内存实现,每个进程只有一个线程,一般用于单机系统开发。

     多线程:在同一个进程下的所有线程可以共享内存和变量。

   而共同点是,同开辟的进程数/线程数多于系统cpu核数时,无法继续提高应用的性能。

   而多线程架构的服务器,只要适当将一些任务分出来用新的进程启动,就可以扩展成分布式架构,使用tcp通信即可。当然多进程也可以这么干,通信方式也是使用tcp。

     而操作系统对于线程的切换是比进程的切换要快。

 

下面先介绍下多进程单线程服务器架构,以单机系统为例:

  先贴架构图:

  

一个游戏服大概就有这几个进程。

router:作用如其名,路由。 每个功能进程启动时,会先连接router,router会给连上来的进程分配一个唯一标识,所有功能进程都是靠这个router进程通信。

login: 登录服务器,client登录验证在这个进程进行。

logic:玩家单人逻辑操作处理进程,login会将登录的玩家平摊到这些logic上。

global_logic: 全局操作进程,多人玩法的功能,例如战斗匹配,工会等操作会放在这里进行。

log:游戏日志输出进程,所有功能进程的日志输出都先发到这个进程,log进程再输出到磁盘文件,

db: redis作为内存数据库,mysql作为数据持久化,其他功能进程取数据都会发送请求到db。

back: 后台进程,集成了一个http服务器,可以处理http请求,这里可以集成一些第三方服务功能,如gm指令。

 

以上每个进程都是单线程,所以无需考虑锁的问题。

对于每个进程收发数据:

  发数据:直接把 {target_id: data} 发送到router,

  收数据:帧驱动,如100ms主动向router询问是否有数据,有则取过来进行处理。

单机系统下,如果采用共享内存方式,通信效率将非常高。

所以多进程的服务器架构设计起来还是比较简单的。

 

再介绍下多线程服务器的架构,这里我想介绍actor模型。

 

一个Actor指的是一个最基本的计算单元。它能接收一个消息并且基于其执行计算。

这个理念很像面向对象语言,一个对象接收一条消息(方法调用),然后根据接收的消息做事(调用了哪个方法)。

Actors一大重要特征在于actors之间相互隔离,它们并不互相共享内存。这点区别于上述的对象。也就是说,一个actor能维持一个私有的状态,并且这个状态不可能被另一个

actor所改变。

每个Actor都有一个邮箱,用于接收其他actor发送的消息。

 

这里重点要讲下Actor模型的调度是怎样做的。

Actor模型实际上可以有成千上万个,但目前一台通用服务器最多只有24核,当然不可能也开成千上万个线程。

 

我们可以把Actor简单想象成这样一个类实例:

class Actor
{
public:
void process_1();
void process_2();
void fetch_msg(); private: int actor_id; string actor_name;
list<msg> msg_queue; }

 每个Actor定义了自己实现的功能(process_1, process_2).

当msg_queue邮箱有消息到来的时候,就调用fetch_msg去获取这些消息进行处理。

这一步就是靠调度线程来做了。

 

Actor模型的调度实现起码要有:

  1. 一个位于主线程的Actor队列,如 global_queue<Actor*> gq, 当某个Actor收到消息时,就会被放进这个gq,等待工作线程进行调度。

      2. n个工作线程,这个就要根据机器的核数来决定开多少个了,例如只是一台双核的机器,那么开一个就好了,开多了就会浪费时间在线程切换上,得不偿失。

每个工作线程做的事件很简单,向主线程询问任务,获取任务,处理任务,然后又继续询问,大致如下:

while(true)
{
task_list = fetch_task();
process_task(task_list);
}

 所以一个Actor的创建和调度过程如下:

1. 在主线程创建并放入管理列表.

2. 其他actor往本actor发送消息,消息进入msg_queue,本actor进入 global_queue等待调度。

3. 有工作线程处理完一堆任务了,向主线程询问任务,主线程把本actor分配给这个工作线程。

4. 该工作线程取出msg,调用actor相应处理函数处理这个消息。

 

所以可见,actor数目与工作线程数目没有必然的关系,当然理想状态是,每个actor都有自己的处理线程,这里有消息来到时,就可以马上处理,不用等待。

 

理论上,actor开的越多,业务逻辑就分的越细,每次处理的时间就越短,只要actor的数目超过线程数,就可以最大限度利用多核的优势,cpu的调度就越充分。

所以actor模型设计关键在于如何将业务逻辑平摊到更多的actor上,而不是集中。 例如上面提到global_logic是多人玩法的业务逻辑,只要一细分,可以把分成

帮会actor,组队actor,战斗actor等等,这样三个消息同时就有机会被三个cpu处理,而不是固定只有一个。

 

Actor可以理解成用户级别的进程,与操作系统级别的进程分离,即使开很多Actor,只要工作线程数目设计合理( <= 系统cpu核数),就能保证线程能在一直同一个cpu上

进行操作,减少线程切换的消耗,这对于cpu核数小的机器非常有用。 而对于像24核的机器,因为开辟的线程数是配置的,所以也很好规划一台机器能部署多少个服。

而多进程如果要对某些功能进行扩展,如增加login,增加logic,就是要增加一个系统线程,一旦进程数超过了cpu核,就会有时间浪费在切换线程上了,

这是一个缺点。

而Actor模型本身是优秀的,但是Actor的调度算法有会有很多种实现,而且必然涉及到锁的设计,这就需要设计者的设计功力了。

posted @ 2017-01-01 11:55  逸马闪骑  阅读(5814)  评论(0编辑  收藏  举报