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

本文涉及的产品
函数计算FC,每月15万CU 3个月
可观测监控 Prometheus 版,每月50GB免费额度
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: 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组件无配置项。

                    相关实践学习
                    通过日志服务实现云资源OSS的安全审计
                    本实验介绍如何通过日志服务实现云资源OSS的安全审计。
                    相关文章
                    |
                    11月前
                    |
                    存储 缓存 JavaScript
                    node 框架
                    Node 框架以其独特的优势和广泛的应用场景,成为了现代 Web 开发中不可或缺的一部分。掌握 Node 框架的开发技能,对于开发者来说具有重要的意义。
                    334 59
                    |
                    3月前
                    |
                    安全
                    基于Reactor模式的高性能服务器之Acceptor组件(处理连接)
                    本节介绍了对底层 Socket 进行封装的设计与实现,通过 `Socket` 类隐藏系统调用细节,提供简洁、安全、可读性强的接口。重点包括 `Socket` 类的核心作用(管理 `sockfd_`)、成员函数的功能(如绑定地址、监听、接受连接等),以及 `Acceptor` 组件的职责:监听连接、接收新客户端连接并分发给上层处理。同时说明了 `Acceptor` 与 `EventLoop` 和 `TcpServer` 的协作关系,并展示了其成员变量和关键函数的工作机制。
                    75 2
                    |
                    11月前
                    |
                    Web App开发 JavaScript 前端开发
                    深入浅出Node.js后端框架
                    【10月更文挑战第34天】在数字化时代,后端开发如同一座桥梁,连接着用户界面与数据处理的两端。本文将通过Node.js这一轻量级、高效的平台,带领读者领略后端框架的魅力。我们将从基础概念出发,逐步深入到实战应用,最后探讨如何通过代码示例来巩固学习成果,使读者能够在理论与实践之间架起自己的桥梁。
                    |
                    7月前
                    |
                    人工智能 安全 大数据
                    【限时特惠】阿里云服务器7折抢购!高性能+高性价比,开发者与企业必备攻略
                    阿里云服务器限时7折特惠,高性能、高性价比,为开发者和企业量身打造!新老用户均可参与,灵活配置满足多种需求,全球节点低延迟覆盖。自研神龙架构保障稳定性,安全防护全面,操作便捷,生态丰富。适用于个人开发、企业部署、跨境业务及AI计算等场景。点击专属链接立即抢购,活动名额有限,速来享受云端算力带来的高效体验!
                    149 0
                    |
                    8月前
                    |
                    存储 人工智能 弹性计算
                    2025年阿里云企业高性能云服务器租用价格与选型详解
                    随着企业数字化转型,阿里云于2025年推出多款高性能云服务器实例,涵盖计算、通用和内存密集型场景。文章分析了企业选择云服务器的核心要点,包括明确业务需求(如计算密集型任务推荐计算型实例)、性能与架构升级(如第八代实例性能提升20%),以及第九代实例支持AI等高算力需求。同时提供了配置价格参考和成本优化策略,助力企业实现效率与成本的最优平衡。
                    |
                    11月前
                    |
                    Web App开发 缓存 监控
                    如何解决Node框架中内存管理的挑战?
                    解决 Node 框架中内存管理的挑战需要综合运用多种方法,并且需要在开发过程中保持谨慎和细心,不断优化和改进代码。同时,定期进行内存管理的检查和维护也是非常重要的。
                    205 63
                    |
                    11月前
                    |
                    存储 JavaScript 搜索推荐
                    Node框架的安装和配置方法
                    安装 Node 框架是进行 Node 开发的第一步,通过正确的安装和配置,可以为后续的开发工作提供良好的基础。在安装过程中,需要仔细阅读相关文档和提示,遇到问题及时解决,以确保安装顺利完成。
                    586 58
                    |
                    11月前
                    |
                    安全 前端开发 JavaScript
                    Node框架的优缺点
                    Node 框架的优点使其在现代 Web 开发中具有重要地位,但同时也需要开发者在使用过程中注意其缺点,合理选择和应用,以充分发挥其优势,避免潜在问题的出现。随着技术的不断发展和完善,Node 框架也在不断改进和优化,以更好地适应各种应用需求。
                    336 57
                    |
                    11月前
                    |
                    缓存 负载均衡 JavaScript
                    构建高效后端服务:Node.js与Express框架实践
                    在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
                    |
                    11月前
                    |
                    Web App开发 JavaScript 前端开发
                    构建高效后端服务:Node.js与Express框架的实践
                    【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。