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


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

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

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

       而共同点是,同开辟的进程数/线程数多于系统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的调度算法有会有很多种实现,而且必然涉及到锁的设计,这就需要设计者的设计功力了。

    Coding Life
  • 相关阅读:
    Hibernate+JPA (EntityMange讲解)
    JPA和Hibernate的区别
    Hibernate与Jpa的关系,终于弄懂
    JEE学习线路
    J2EE中你必须了解的13种技术规范
    js设置datagriad的行移动
    js正则表达式中的特殊字符
    iOS 开发之动画篇
    Phone APP设计规范/iPad APP设计规范/Android APP设计规范/网页设计规范
    打包程序时的证书问题(上传APP就出现Missing iOS Distribution signing indetity for)
  • 原文地址:https://www.cnblogs.com/lewiskyo/p/6240854.html
Copyright © 2020-2023  润新知