一 概述
游戏服务器的本质就是基于长连接的socket服务器。 它利用socket通讯来实现服务器与客户端之间的交互。事实上有不少游戏是直接基于原生socket来开发的。
相对于简单的socket服务器,它承受着更加繁重的任务:
- 后端承载着极复杂的游戏逻辑。
- 网络流量与消息量巨大,且实时性要求高。
- 通常一台socket服务器无法支撑复杂的游戏逻辑,因此往往使用一个服务器集群来提供服务。
游戏应用只能使用长连接,原因如下:
- 通讯的双向性,游戏应用不仅仅是推拉模式,而且推送的数据量要远远大于拉的数据量
- 响应的实时性要求极高,一般游戏应用要求推送的消息实时反应,而实时响应的最大时间是100ms。
二 场景分区
游戏交互跟玩家所在地图(场景)上的位置关系非常大,如两个玩家在相邻的地方可以互相PK或组队打怪。这种相邻的交互频率非常高,对实时性的要求也非常高,这就必须要求相邻玩家在分布在同一个进程里。于是就有了按场景分区的策略,即一个进程里可以有一个场景,也可以有多个场景。
这种实现带来了游戏的可伸缩性受到场景进程的限制,如果某个场景过于烦忙可能会把进程撑爆,也就把整个游戏撑爆。场景服务器是有状态的,每个用户请求必须发回原来的场景服务器。服务器的有状态带来一系列的问题:场景进程的可伸缩,高可用性等都比不上web服务器。目前只能通过游戏服务器的隔离来缓解这些问题。
游戏的基于场景(area)的分区模式, 使同场景的玩家跑在一个进程内, 以达到最少的跨进程调用。
三 可伸缩性
可伸缩性(可扩展性)是一种对软件系统计算处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展成长过程中,通过很少的改动甚至只是硬件设备的添置,就能实现整个系统处理能力的线性增长,实现高吞吐量和低延迟高性能。
对于游戏服务器,可伸缩性始终是最重要的指标,也是最棘手的问题,它涉及到系统运行架构的搭建,各种优化策略。 只有把可伸缩性设计好了,游戏的规模、同时在线人数、响应时间等参数才能得到保证。
游戏服务器是蜘蛛网式的架构,每个进程都有各自的职责,这些进程的交织在一起共同完成一件任务。因此游戏服务器是一个标准的分布式开发架构。
四 实时性
对实时游戏服务器来说,常见的实时性很高的任务有:
实时Tick
实时游戏的服务端一般都需要一个定时tick来执行定时任务,为了游戏的实时性,一般要求这个tick时间在100ms之内。这些任务一般包括以下逻辑:
- 遍历场景中的实体(包括玩家、怪物等),进行定时操作,如移动、复活、消失等逻辑。
- 定期补充场景中被杀掉的怪的数量。
- 定期执行AI操作,如怪物的攻击、逃跑等逻辑。
由于实时100ms的限制,这个实时tick的执行时间必须要远少于100ms。
广播
由于玩家在游戏里的行动要实时地通知场景中的其它玩家, 必须通过广播的模式实时发送。这也使游戏在网络通信上的要求高于web应用。游戏中广播的代价是非常大的。玩家的输入与输出是不对等的,玩家自己简单地动一下,就需要将这个消息实时推送给所有看到这个玩家的其他玩家。 假如场景里面人较少,广播发送的消息数还不多,但如果人数达到很密集的程度,则广播的频度将呈平方级增长。
假如场景中1000个玩家,每人发1条消息,如果需要其它玩家都看到的话,消息的推送量将高达1,000,000条,这足以把任何服务器撑爆。
五 分布式
游戏服务器分布式开发的主要难点有:
多进程管理、RPC调用、分布式事务、异步操作、负载均衡和高可用
由于游戏服务器的有状态性,很多请求需要通过特定的路由规则导到某台服务器;对于有些无状态的服务器,我们则可以把请求路由到负载最低的服务器。通常对于无状态的服务器, 高可用是比较好做的。对于有状态的服务器,要做高可用会非常困难, 但也不是完全没有办法,常见的方法是:
- 将状态引出到外存
例如redis, 这样进程本身就可以无状态了。但由于所有的操作都通过redis可能带来性能损耗,有些场景是不能承受这些损耗的。
- 通过进程互备
将状态通过日志等方式同步到另一进程, 但这可能存在着瞬间数据丢失的问题,这种数据丢失在一些应用场景可能毫无问题, 但在另外一些应用场景可能引起严重的数据不一致。
六 游戏服务器的运行架构
一个典型的多进程MMO运行架构,如上图所示
- 上图中的方块表示进程, 定义上等同于“服务器”;
- 客户端通过websocket长连接连到connector服务器群;
- connector负责承载连接,并把请求转发到后端的服务器群;
- 后端的服务器群主要包括按场景分区的场景服务器(area)、聊天服务器(chat)和状态服务器(status)等, 这些服务器负责各自的业务逻辑。真实的案例中还会有各种其它类型的服务器;
- 后端服务器处理完逻辑后把结果返回给connector, 再由connector服务器broadcast/response回给客户端;
- master负责统一管理这些服务器,包括各服务器的启动、监控和关闭等功能。
七 服务器的抽象与分类
游戏服务器可以抽象成两类:前端服务器和后端服务器, 如图:
前端服务器(frontend)的职责:
- 负责承载客户端请求的连接
- 维护session信息
- 把请求转发到后端
- 把后端需要广播的消息或响应发送到客户端
后端服务器(backend)的职责:
- 处理业务逻辑, 包括RPC和前端请求的逻辑
- 把消息推送回前端或者将对客户端请求的响应发送到前端服务器
服务器的对外接口只有两类, 一类是接收客户端的请求, 叫做handler, 一类是接收RPC请求, 叫做remote, handler和remote的行为决定了服务器长什么样子。 因此我们只要定义好handler和remote两类的行为, 就可以确定这个服务器的类型。
(摘自Pomelo官方文档)