nodejs循序渐进-高性能游戏服务器框架pomelo之启动流程和组件

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
函数计算FC,每月15万CU 3个月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: nodejs循序渐进-高性能游戏服务器框架pomelo之启动流程和组件

游戏启动过程

启动入口

在使用pomelo进行游戏开发时,工程目录下的app.js是整个游戏服务器的启动运行入口。app.js中创建项目,进行默认配置并启动服务器的代码如下:

var pomelo = require('pomelo');
var app = pomelo.createApp();
app.set('name', 'nameofproject');
app.start();

image.gif

从上面的代码可以看出,用户首先需要在项目中引入pomelo,然后创建application的实例app,接着完成一些基本的应用配置,最后应用就可以启动了。当app.js运行起来后,pomelo会根据游戏的配置启动不同的相关组件及服务器。

服务器与组件

服务器启动流程的主要工作就是逐一启动app.load注册的组件。组件是连接pomelo框架和当前服务器所依赖的服务之间的桥梁。不同的服务器可以选择加载不同的组件。Pomelo提供了一些系统默认的组件,主要包括:handler, filter, master, monitor, proxy, remote, server, sync, connection。开发者也可以根据需要,开发自己的组件,并加载到服务器进程中。

组件同时是具有生命周期的,其生命周期可以包括start, after start, stop等。在组件中可以实现这些方法,应用服务器会在不同的运行阶段执行组件不同生命周期的方法。

启动流程详述

应用创建及启动

所有服务器的启动都是从运行app.js开始。每一个服务器的启动都首先创建一个全局唯一的application对象,该对象中挂载了所在服务器的所有信息,包括服务器物理信息、服务器逻辑信息、以及pomelo的组件信息等。同时,该对象还提供应用管理和配置等基本方法。 在app.js中调用app.start()方法后,application对象首先会通过loadDefaultComponents方法加载默认的组件。

组件加载

在加载组件时,系统会根据application对象中服务器的信息,针对不同的服务器加载不同的组件,从而使得不同服务器进程对外提供不同个服务。对于master服务器,主要加载的组件是master组件。Master组件主要负责根据根据servers.json文件中的配置信息和启动参数去启动其他服务器。对于其它服务器默认加载proxy、channel、sync、backendSession和server组件,特定的服务器还需要加载特定的组件,例如前端服务器会加载统计客户端连接数量的connection组件。具体组件的说明如下:

    • master: master组件主要负责启动master服务器。
    • monitor: monitor组件主要负责启动各个服务器的monitor服务,该服务负责收集服务器的信息并定期向master进行消息推送,保持master与各个服务器的心跳连接。
    • proxy: proxy组件主要负责生成服务器rpc客户端,由于系统中存在多个服务器进程,不同服务器进程之间相互通信需要通过rpc调用(master服务器除外)。
    • remote: remote组件主要负责加载后端服务器的服务并生成服务器rpc服务端。
    • server:server组件主要负责启动所有服务器的用户请求处理服务。
    • connector: connector组件主要负责启动前端服务器的session服务和接收用户请求。
    • sync: sync组件主要负责启动数据同步模块并对外提供数据同步功能。
    • connection: connection组件主要负责启动用户连接信息的统计服务。
    • channel: channel组件主要负责启动channelService服务,该服务主要提供channel相关的功能包括创建channel,通过channel进行消息推送等。
    • session: session组件主要负责启动sessionService服务,该服务主要用来对前端服务器的用户session进行统一管理。
    • backendSession: backendSession组件主要负责启动backendSession服务,该服务主要负责维护服务器本地session并与前端服务器进行交互。
    • dictionary: dictionary组件主要负责生成handler的字典。
    • protobuf: protobuf组件主要负责解析服务端和客户端的proto buffer的定义,从而对客户端和服务端的通信内容进行压缩。

    组件概述

    我们知道,pomelo的应用程序执行的全部过程,就是对其相应组件的生命周期的管理,实际的所有逻辑功能均由pomelo组件提供。pomelo内建提供了十多个组件,这些组件适用于不同的服务器,提供不同的功能。有些组件提供的功能比较复杂,有些则比较简单。下面我们将以提供的功能为主线来阐述pomelo提供的内建组件。

    master组件

    master组件仅仅由master服务器加载,它主要的功能包括启动所有的应用服务器、管理和监控所有的应用服务器和接受管理客户端的请求与响应。

    master服务器总是率先启动,master组件在其start调用的最后才会调用Starter.start,Starter.start才会启动所有的应用服务器,因此Master组件总是最先start。在Master组件的start调用中,会完成以下几步:

      • 加载注册Module到MasterConsoleService,Module的导出方式有两种,可以导出工厂函数,也可以导出对象,如果导出工厂函数的话,其签名应该是 FacFunc(opts, ConsoleServicee),其中opts是用户调用app.registerAdmin的时候传入的,ConsoleService则是具体的加载注册Module的MasterConsoleService。
      • 在加载注册完所有的Module后,会开启MasterAgent对端口的监听,此时,master就已经可以接收来自monitor和client的request和notify了。
      • 开启监听后,MasterConsoleService会enable所有的module,这步操作主要是看看有没有module配置了周期性地拉去monitor信息,也就是module的配置中有type选项和interval选项,且type的值为'pull',interval指定了周期,则认为其配置了周期性监控操作,此时会完成周期性事件的调度,使得master可以周期性地获取监控信息。
      • 最后如果有Module定义了start回调,将会在这里调用,一般在start回调里会做一些初始化信息。 经历了这些步骤后,master完成启动。

      在master组件的start方法里,会根据用户提供的服务器配置信息,启动用户配置的所有的具体应用服务器。

      当master组件start结束后,他将开启一个socket监听端口,接受应用服务器和监控客户端的连接和注册,收集应用服务器上报的监控信息,给应用服务器推送一些消息,并对管理客户端发出的管理请求给予响应。管理客户端如pomelo-cli可能发出的请求包括查看某个服务器进程状态,增加一个服务器,停掉一个服务器等。以增加一个服务器为例,当管理客户端发出增加服务器请求时,会提供相应的服务器参数,如服务器类型,主机ip,开启的端口等。此时,master组件接受后,会启动相应的服务器,并将新增加的服务器信息广播通知给其他已经启动的服务器。

      master组件无配置项。

      monitor组件

      monitor组件由所有的包括master服务器在内的服务器都会加载,它的主要功能就是与master建立连接进行通信,从而对整个应用服务器群进行管理和监控。master服务器本身也会加载monitor服务器,因为master服务器也会收集其本身自己的监控信息。

      由于应用服务器是在master组件启动后期才创建,因此monitor总是后于master启动。monitor的启动过程与master类似,唯一不同的就是,monitor会发起到master的连接,而不是监听接口。monitor中同样也会使用与master完全相同的方式,加载注册Module,如果有Module配置了周期性地推送监控数据到master的话,即其配置type的值为'push',这里也会调度对应的事件,使得能够周期地推送数据。最后如果有Module定义了start的话,则会回调start。Monitor的启动过程与master基本一致。

      monitor会通过master接受一些命令,比如关闭整个服务器等。对于一些周期性监控的信息,pomelo提供了两种收集方式,即pull方式和push方式。pull方式要求master周期地去与monitor通信,拉取相应的监控信息;push方式,则是由monitor周期地主动地向master报告其监控信息。

      monitor组件无配置项。

      filter

      pomelo内建了常见的一些filter,用户可以通过如下的方式启用:

      app.filter(pomelo.filters.<filterName>(<args>));

      image.gif

      下面介绍一下这几个fitler:

      serial

      这个filter是用来对用户请求做串行化的,可以使得用户的请求只有在第一个请求被处理完后,才会处理第二个请求。serial中使用了一个taskManager,当用户请求到来时,在beforeFilter中,将用户的请求放到taskManager中,taskManager中维护着一个task队列。在对应的afterFilter中,如果taskManager还有未处理的请求,将会处理其请求,即在一个请求的afterFilter里启动在taskManager中还没处理的下一个请求,这样就实现了请求的序列化。

      timeout

      这个filter是用来对服务端处理超时进行警告的,在beforeFilter中会启动一个定时器,在afterFilter中清除。如果在其定时器时间内,afterFilter被调用,定时器将会被清除,因此不会出现超时警告。如果定时器超时时,afterFilter还没有执行到,则会引发超时警告,并记录日志。默认的处理超时是3秒,可以在加载timeout的时候作为参数传入。

      time

      这个filter使用来记录服务器处理时间的,在beforeFilter中会记录一下当前的时间戳,在afterFilter中再次获取当前的时间戳,然后两个时间戳相减,得到整个处理时间,然后记录日志。

      toobusy

      这个filter中,一旦检测到node.js中事件循环的请求等待队列过长,超过一个阀值时,就会触发toobusy。一旦触发了toobusy,那么toobusy的filter中将终止此请求处理链,并在next调用中,传递错误参数。

      connector组件

      connector组件是一个重量级的组件,它会依赖于session组件,server组件,pushScheduler组件和connection组件。connector组件仅仅被前端服务器加载,它主要用来管理客户端的连接。connector组件会加载底层的connector,创建端口监听,绑定事件响应。当有客户端连接请求时,connector组件会请求session组件,获得当前连接的session,如果session组件中没有相应的session的话,session组件会为这个新连接创建新的session,并维护相应的连接;然后connector组件还会向connection组件上报连接信息,供统计使用;最后,将拿到的session以及客户端的请求,一起抛给server组件,由server组件进行请求处理。当server组件处理完请求后,又会通过connector组件将响应返回给客户端。在返回响应给客户端的时候,connector组件做了一个缓存选择,这个缓存实现依赖于pushScheduler组件,也就是说connector组件并不是直接将响应发给客户端,而是将响应给pushScheduler组件。pushScheduler组件根据相应调度策略,可能不缓存直接通过session组件维护的连接,将响应发出去,也可能进行缓存,并按时flush。这是可以配置的。

      connector组件支持如下配置项:

        • connector: 底层使用的通信connector,不配置的话,会默认使用sioconnector;
        • useProtobuf: 目前仅仅支持connector配置使用hybridconnector的情况,配置其为true,将开启消息的protobuf功能;
        • useDict: 目前仅仅支持connector配置使用hybridconnector的情况,配置其为true时,将会开启基于字典的路由消息压缩;
        • useCrypto: 目前仅仅支持connector配置为hybridconnector的情况,配置其为true时,将会启用通信时的数字签名;
        • encode/decode: 消息的编码解码方式,如果不配置的话,将会默认使用connector配置中,底层connector提供的相应的编码解码函数。
        • transports:这个配置选项是用于sioconnector的,因为socket.io的通信方式可能会有多种,如websocket,xhr-polling等等。通过这个配置选项可以选择需要的方式。

        配置connector组件,通过调用如下方式进行:

        app.set('connectorConfig', opts);

        image.gif

        session组件

        session组件跟connector相关,也是仅仅被前端服务器加载,为sessionService提供一个组件包装, 加载session组件后,会在app的上下文中增加sessionService,可以通过app.get('sessionService')获取。它主要用来维护客户端的连接信息,以及生成session并维护session。如果与经典TCP进行类比的话,那么session中维护的连接就可以粗略地认为就是TCP服务器端accept返回的socket句柄。一个连接与一个session对应,同时session组件还维护具体登录用户与session的绑定信息。一个用户可以有多个客户端登录,对应于多个session。当需要给客户端推送消息或者给客户端返回响应的话,必须通过session组件拿到具体的客户端连接来进行。

        session组件支持如下配置项:

          • singleSession: 如果这个配置项配置为true的话,那么将将不允许一个用户同时绑定到多个session,在绑定用户一次后,后面的绑定将会失败。

          配置session组件,通过调用如下方式进行:

          app.set('sessionConfig', opts);

          image.gif

          connection组件

          connection组件是一个功能相对简单的组件,也是仅仅被前端服务器加载,为connectionService提供一个组件包装,他主要进行连接信息的统计,connector组件接收到客户端连接请求以及有客户端离线时,以及用户登录下线等等情况,都会向其汇报。

          connection组件无配置项。

          server组件

          server组件也是一个功能比较复杂的组件,它被除master外的服务器加载。server组件会加载并维护自身的Filter信息和Handler信息。server组件会从connector组件的回调里获得到相应的客户端请求或者通知,然后会使用自己的before filters对其消息进行过滤,再次调用自己的相应Handler进行请求的逻辑处理,然后将响应通过回调的方式发给connector处理。最后调用after filters进行一些清理处理。

          当然,如果客户请求的服务本来就是前端服务器提供的话,会是上面的那种处理流程。如果客户请求的服务是后端服务器提供的服务的话,则将不是上面的那种处理流程,此时会出现sys rpc调用。前面那种前端服务器自己处理的情况具体调用为doHandle,而发起rpc调用的情况则为doForward。这两种处理流程的不同点是,对于自身的请求,调用自己的filter-handler链进行处理,对于不是前端服务器自己提供的服务,则是发起一个sys rpc,然后将rpc调用的结果作为响应,发给connector进行处理。关于这个rpc调用则是pomelo内建的msgRemote实现的。

          对于后端服务器来说,其客户请求不是直接来源于真实的客户端,而是来源于前端服务器对其发起的sys rpc调用,这个rpc调用的实现就是pomelo内建的msgRemote,在msgRemote的实现里,会将来自前端服务器的sys rpc调用请求派发给后端服务器的server组件,然后后端服务器会启用filter-handler链对其进行处理,最后通过rpc调用的返回将具体的响应返回给前端服务器。

          在前端服务器将客户端请求向后端服务器分派时,由于同类型的后端服务器往往有很多,因此需要一个路由策略router,一般情况下用户通过Application.route调用为后端服务器配置router。

          server组件无配置项。

          pushScheduler组件

          pushScheduler组件也是一个功能较为简单的组件,它仅仅被前端服务器加载,与connector组件的关系密切。当connector组件收到server组件的对客户端请求的响应后,connector并不直接将此响应返回给客户端,而是将这个给客户端发送响应的操作调度给scheduler组件。pushScheduler组件完成最后通过session组件拿到具体的客户端连接并将请求的响应发送给客户端的任务。因此,通过pushScheduler组件可以对发给用户的响应进行缓冲,从而提高通信效率。pomelo实现了两种调度策略,一种是不进行任何缓冲,直接将响应发送给客户端,一种是进行缓冲,并定时地将已缓冲的响应发送给对应的客户端。

          pushScheduler配置项:

            • scheduler: scheduler组件的具体调度策略配置,默认的是直接将响应发给客户端,同时pomelo还提供了有缓冲并且定时刷新的调度策略。用户也可以自定义自己的调度策略。

            配置pushScheduler组件,通过调用如下:

            app.set('pushSchedulerConfig', opts);

            image.gif

            如果要启用使用缓冲的scheduler的话,可以在app.js中增加:

            app.set('pushSchedulerConfig', {scheduler: pomelo.pushSchedulers.buffer, flushInterval: 20});

            image.gif

            flushInterval是刷新周期,默认为20毫秒。

            proxy组件

            proxy组件是一个重量级的组件,它被除master外的所有服务器加载。proxy组件会扫描具体应用服务器的目录,抽取其中的remote部分,由于javascript语言的动态性,可以很轻易地获得到remote中的关于远程调用的元信息,生成stub,并将这些调用都挂到app.rpc下面,当用户发起rpc调用时,proxy组件会查看其扫描到的stub信息,以此决定此远程调用是否合法。同时,proxy又会创建一个RpcClient,当发起远程调用时,负责与远端的remote进行通信,并得到远程调用的结果供调用者使用。当进行远程调用时,由于同类型的远程服务器可能有多个,所以这里同样需要配置相应的router。

            proxy的配置项:

              • cacheMsg, 配置cacheMsg为true的话,将开启rpc调用时的对消息的缓冲,而不是直接一旦有rpc请求就发出。
              • interval, 与配置参数cacheMsg配合使用,设置flush缓存的周期
              • mailBoxFactory, rpc底层实现需要的,用户可以定义自己的mailBoxFactory,我们将在rpc原理里面详述。

              另外,可以开启rpc的调用日志,通过如下的调用:

              app.enable('rpcDebugLog');

              image.gif

              配置proxy使用:

              app.set('proxyConfig', opts);

              image.gif

              remote组件

              remote组件是与proxy组件对等的组件,它用来提供rpc调用服务。rpc组件完成对当前服务器的remote的加载,并开启监听端口,等待rpc客户端的连接及相应的rpc调用。当接收到具体的调用请求时,会根据调用请求中描述的调用请求信息,调用相应的remote中的相应方法。然后再将具体的处理结果返回给rpc客户端。rpc服务端还支持对调用请求的filter,也就是说跟server组件处理客户端请求一样,rpc服务端处理具体请求时也会使用filter-remote链进行处理。

              remote组件配置项:

                • cacheMsg, 与proxy组件的含义相同
                • interval, 与proxy组件的含义相同
                • acceptorFactory, rpc底层实现需要的,可以认为跟proxy配置中的mailBoxFactory是对等的,我们将在rpc原理里面详述。

                跟proxy组件一样,用户可以开启rpcDebugLog来得到所有的rpc调用过程的日志。 配置remote组件使用:

                app.set('remoteConfig', opts);

                image.gif

                dictionary组件

                dictionary组件是一个可选组件,不会被默认加载,只有当connector组件的配置中开启了useDict的时候,此组件才会被加载。此组件会遍历所有handler的route字符串,还会从config/dictionary.json中读取客户端的route字符串,然后对这些字符串进行编码,给予每一个路由赋予一个唯一的小整数,实现route信息压缩,当客户端与前端服务器通信时需要路由信息时,将不会再使用很长的那个字符串,而仅仅使用一个小整数。

                dictionary的配置项:

                  • dict, 客户端路由字符串文件的位置,默认使用的是config/dictionary.json 配置dictionary组件使用:
                    app.set('dictionaryConfig', opts);

                  protobuf组件

                  protobuf组件也是一个可选组件,不会被默认加载,只有当connector组件的配置中开启了useProtobuf的时候,此组件才会被加载。此组件会加载对应的proto文件,并完成消息的基于protobuf的编解码。默认的proto文件的配置信息在config/serverProtos.json和config/clientProtos.json中。具体会在详细介绍pomelo-protobuf中详细介绍。

                  protobuf组件无配置项。

                  channel组件

                  channel组件维护channel信息,可以被除了master之外的服务器加载。channel组件可以看作是channelService的组件包装,加载该组件后,会在app上下文中加入channelService,可以通过app.get('channelService')获取。可以认为一个channel就是一个用户的集合,每一个用户大致对应于前端服务器中的一个session,用户可以通过channel组件向一个channel里面的所有用户推送消息。当然,由于后端服务器并不与客户端直接相连,故后端服务器会发起一个sys rpc来表示向客户端推送消息,接受这个远程调用的是pomelo已经实现的ChannelRemote。

                  channel组件的配置项:

                    • broadcastFilter, broadcast的过滤函数。会在执行channel的broadcast的时候,在前端服务器上,在消息发送给每个session之前,进行一个过滤。其函数签名为
                      broadcastFilter(session, msg, filterParam)

                    其中filterParam参数由在channelService的broadcast调用时传入,如下:

                    channelService.broadcast(type, route, {filterParam: param}, cb);

                    image.gif

                    可以通过如下方式对Channel组件进行配置:

                    app.set('channelConfig', opts)

                    image.gif

                    backendSession组件

                    BackendSession组件可以看作是BackendSessionService的组件包装,加载该组件后,会在app的上下文中加入backendSessionService,可以通过app.get('backendSessionService')调用获取。可以被除了master之外的服务器加载。它主要为后端服务器提供BackendSession信息,并通过远程过程调用完成一些比如对原始session绑定uid等操作。

                    backendSession组件无配置项。

                    相关实践学习
                    日志服务之使用Nginx模式采集日志
                    本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
                    相关文章
                    |
                    3月前
                    |
                    Python
                    Flask学习笔记(二):基于Flask框架上传图片到服务器端并原名保存
                    关于如何使用Flask框架上传图片到服务器端并以其原名保存的教程。
                    100 1
                    |
                    3月前
                    |
                    Python
                    Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
                    这篇博客文章是关于如何使用Flask框架上传特征值数据到服务器端,并将其保存为txt文件的教程。
                    38 0
                    Flask学习笔记(三):基于Flask框架上传特征值(相关数据)到服务器端并保存为txt文件
                    |
                    3月前
                    |
                    分布式计算 Hadoop
                    Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
                    Hadoop-27 ZooKeeper集群 集群配置启动 3台云服务器 myid集群 zoo.cfg多节点配置 分布式协调框架 Leader Follower Observer
                    62 1
                    |
                    3月前
                    |
                    Web App开发 JavaScript 前端开发
                    使用Node.js和Express框架构建Web服务器
                    使用Node.js和Express框架构建Web服务器
                    |
                    3月前
                    |
                    存储 SQL 消息中间件
                    Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
                    Hadoop-26 ZooKeeper集群 3台云服务器 基础概念简介与环境的配置使用 架构组成 分布式协调框架 Leader Follower Observer
                    56 0
                    |
                    3月前
                    |
                    存储 Linux 网络安全
                    【VMware VCF】使用 SFTP 服务器备份 VCF 核心组件的配置文件
                    【10月更文挑战第6天】以下是使用 SFTP 服务器备份 VMware VCF 核心组件配置文件的步骤:首先,设置 SFTP 服务器并配置用户账号与权限;其次,确保 VCF 环境能与 SFTP 服务器建立网络连接,并检查防火墙规则;接着,识别核心组件配置文件的位置,并使用 SFTP 客户端工具(如 `lftp` 或 WinSCP)进行备份;最后,验证备份结果的完整性和正确性,并定期执行备份操作,设置备份文件保留策略以节省存储空间。
                    |
                    5月前
                    |
                    Rust 安全 开发者
                    惊爆!Xamarin 携手机器学习,开启智能应用新纪元,个性化体验与跨平台优势完美融合大揭秘!
                    【8月更文挑战第31天】随着互联网的发展,Web应用对性能和安全性要求不断提高。Rust凭借卓越的性能、内存安全及丰富生态,成为构建高性能Web服务器的理想选择。本文通过一个简单示例,展示如何使用Rust和Actix-web框架搭建基本Web服务器,从创建项目到运行服务器全程指导,帮助读者领略Rust在Web后端开发中的强大能力。通过实践,读者可以体验到Rust在性能和安全性方面的优势,以及其在Web开发领域的巨大潜力。
                    48 0
                    |
                    3天前
                    |
                    机器学习/深度学习 人工智能 PyTorch
                    阿里云GPU云服务器怎么样?产品优势、应用场景介绍与最新活动价格参考
                    阿里云GPU云服务器怎么样?阿里云GPU结合了GPU计算力与CPU计算力,主要应用于于深度学习、科学计算、图形可视化、视频处理多种应用场景,本文为您详细介绍阿里云GPU云服务器产品优势、应用场景以及最新活动价格。
                    阿里云GPU云服务器怎么样?产品优势、应用场景介绍与最新活动价格参考
                    |
                    2天前
                    |
                    存储 运维 安全
                    阿里云弹性裸金属服务器是什么?产品规格及适用场景介绍
                    阿里云服务器ECS包括众多产品,其中弹性裸金属服务器(ECS Bare Metal Server)是一种可弹性伸缩的高性能计算服务,计算性能与传统物理机无差别,具有安全物理隔离的特点。分钟级的交付周期将提供给您实时的业务响应能力,助力您的核心业务飞速成长。本文为大家详细介绍弹性裸金属服务器的特点、优势以及与云服务器的对比等内容。
                    |
                    9天前
                    |
                    人工智能 JSON Linux
                    利用阿里云GPU加速服务器实现pdf转换为markdown格式
                    随着AI模型的发展,GPU需求日益增长,尤其是个人学习和研究。直接购置硬件成本高且更新快,建议选择阿里云等提供的GPU加速型服务器。
                    利用阿里云GPU加速服务器实现pdf转换为markdown格式