游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

2021-01-11  本文已影响0人  996小迁

用通俗的方法来描述一个好的服务端架构,最基础也是最重要的就两点: 支持百万玩家同时在线,不出问题 。这两点也就分别对应了高并发和高可用。

这篇文章系统的介绍游戏服务端中的高并发和高可用。

高并发和高可用是一个相辅相成的工作,当我们支持百万玩家同时在线时却无法保证服务器稳定可用,那高并发支持就无从谈起;而如果当玩家数量较多时服务器就常常出问题,那也不能称为高可用。

一、水平扩展

水平扩展是高并发和高可用的基础,通过支持水平扩展,我们理论上可以通过增加机器获得无限的承载上限,从而支持高并发;在此基础上,若某个进程出现异常,其他进程可以替代其提供服务,从而实现了高可用。

以下图为例,对于不支持水平扩展的架构,游戏服务器中只有一个战斗进程为所有的玩家提供战斗服务,这里存在两个问题:1.一个进程最多只能使用一个一台机器的计算资源,存在性能上限。2.若这个进程或者所在机器/网络发生异常,那么整个系统就不可用了。

游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

不支持水平扩展

水平扩展有两种常见的实现模型:

游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

水平扩展的两种模型

1.1 有状态 vs. 无状态

从进程内存中是否保存状态的角度可以将服务分为有状态和无状态:

无状态服务本身不保存状态,所以进程crash也不会丢失信息。此外,下文将介绍,由于使用随机分配的路由方式,无状态服务对异常的容忍更加好,所以,从高可用角度,无状态更加好。

但是,由于无状态不保存状态,所有状态操作都是数据库操作,就造成了开发成本更高(代码写起来更复杂)、数据库压力更大,所以无状态并不适合所有服务,一般对于状态简单明确的服务,可以优先使用无状态,比如好友服务。

1.2 路由策略

对于有状态和无状态服务,他们使用的路由方式也不同。

对于无状态服务,一般使用 随机分配 的路由方式。随机分配的路由方式有很大的好处,如果某个进程crash了或者网络出现了故障,我们只需要把这个进程从路由中去掉,对后续的请求不会有影响,只会影响此进程当前正在处理的逻辑。

有状态服务的路由需要明确每个请求给哪个进程处理,给其他进程其他进程因为没有相关状态信息也无法处理。比如上文提到的战斗服务,路由根据战斗ID将相关请求发给对应战斗所在的进程中才能处理。路由一般使用 取模 或者 一致性哈希 ,一般会优先使用一致性哈希而不是取模,防止故障引起抖动。

1.3 举个例子

下图是某游戏架构的简化版模型,真实的服务器比这个复杂很多,这里主要是为了举个例子。

游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

集群可以分为三类:支持水平扩展的有状态服务、支持水平扩展的无状态服务、不支持水平扩展的单点服务

其中,支持水平扩展的有状态服务和无状态服务的进程数量能占到项目的90%,单点服务很少。在这种架构情况下,我们游戏的承载上限瓶颈在于单点服务,而单点服务逻辑相对比较简单,承载上限很高。此外,支持水平扩展的服务进程出现异常只会影响此进程所服务的玩家,具有较高的可用性。

有状态服务

在我们游戏的服务器集群中,三分之二左右的进程是处理玩家个人逻辑的进程(玩家集群,很多游戏项目叫大厅服务器)。每个进程处理一部分玩家的业务逻辑,通过shading将玩家分配在不同的玩家进程中。

可以通过增加个人逻辑进程数量提升服务器承载量,我们支持不停服增加或减少进程即动态扩容缩容。,这些进程之间就是平等的,不同进程之间没有强依赖关系。当一个进程crash时,他不会影响其他进程的玩家。

除了玩家进程,还有战斗进程、家族进程等类似进程可以这么设计。

上面提到的都是有状态服务,我们需要记录每个玩家/战斗/家族在哪个进程中,此外,若进程出现异常,虽然不会影响其他玩家/战斗/家族,但当前进程中玩家/战斗/家族都会不可用,而且会丢失一些数据。

无状态服务

我们将部分服务使用无状态实现,比如登陆、支付、好友、部分排行榜等。由于无状态服务具相对于有状态对异常更友好、动态扩容缩容模型 更简单,因此有对于一个新的服务我们优先考虑使用无状态,若状态较复杂才考虑使用有状态服务实现。

单点服务

游戏服务中难免出现一些单点服务,比如玩家管理器、集群管理器、家族管理器等,这类服务不具扩展能力,是游戏服务器的承载瓶颈。此外,也不具有高可用性,如果出现异常会导致导致整个游戏集群不可用。

单点服务逻辑普遍简单(复杂逻辑我们都要支持水平扩容),性能承载普遍较高。比如,我们游戏中目前评估的同时在线保守估计应该在50w,此时我们认为我们的一些单点服务会出现满载,导致游戏无法继续扩容。

此外,单点服务数量较少,出现异常的可能性很低。我们游戏上线近两年,我们也只遇到了两次机器宕机,影响的都是非单点进程,没有影响整体的游戏集群可用性。

当然,单点服务也可以改为支持水平扩展的,只是工作量的问题。理论是来说,是能完全消除单点的,只是对于大部分项目来说性价比不高意义不大。

二、高并发

水平扩展的方案是支持高并发的主要手段(也叫可伸缩Scalability),上文已经介绍过了。

下文主要介绍除了水平扩展高并发的其他方案,以及需要注意的点。

2.1 垂直扩展和性能优化

要提升承载能力,一般有两个方案:

垂直扩展在某些场景也有有用。一般来说,对于上文我们提到的单点,如果不好消除或者消除成本很高,可以通过垂直扩展把这个逻辑放在高配机器上,提升单点逻辑的承载。

此外,我们常常是对战斗服进行性能优化,比如使用C++写高消耗模块,但对于大厅服一般不将其作为提升承载的首要手段。这个我们不深入讨论,一方面这个总会有上限,难有质变,另一方面不同游戏优化方案千差万别,都是代码级别的优化。

服务端优化和垂直扩展的目标类似,就是让一台机器能承载更多的玩家/逻辑。

2.2 消除系统单点和逻辑单点

上文介绍的消除单点主要是系统单点,也就是用多个进程而不是一个进程提供服务。

消除系统单点的前提是消除逻辑单点。

举个例子:我们投放一个武器时,要给这个武器生成一个全服唯一的ID以标识此武器。这个ID可以使用一个自增的ID,此时就造成逻辑单点。

对于这种情况,如果游戏中生成武器的频率很低,那么这种方案也可以,但如果武器生成频率很高,因为游戏中所有逻辑都需要去一个地方去申请这个ID,那就可能产生瓶颈。对于这种情况,我们一般可以使用uuid代替自增ID。(这个场景也常见于DB中的自增列,所以一般建议少使用自增)

2.3 数据库承载

当玩家在线量达到一定的量级以后,往往对后端的数据库造成很大的压力。

一般来说,数据库本身具有水平扩展能力,加上分库分表等方案,提高承载能力比较容易。但设计数据库结构时也需要考虑索引、shadkey等问题,不然严重影响数据库性能。此外,要考虑数据库并发读写能力,比如mongo中的MMAPv1存储引擎是doc级别锁,而WiredTiger存储引擎是collection级别锁,两者的并发能力差别极大。

游戏逻辑普遍比较复杂,数据读写量很大,如果每次玩家信息变更都去读写数据库,会造成较大的数据库压力。因此,游戏的玩家服务一般都是有状态服务,玩家上线时将数据从数据库读到内存中,在线期间读写数据都是直接操作内存,下线时或隔段时间去落地到数据库。这种方案大大降低了数据库读写操作,对数据库压力会小很多。

而一些数据读写操作频率较低的服务,可以考虑将服务做成无状态然后每次读写都去操作数据库。

2.4 多集群和跨集群

当游戏服务端达到一定的规模后,往往需要分集群部署,分集群解决的场景:

多集群中需要解决的一个问题是跨集群通信问题,集群内一般是进程间全连接,但集群间如果进程全连接会造成拓扑混乱连接数量爆炸的问题,因此集群间通信一般使用消息总线,所有的集群通过消息总线进行通信。

游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

2.5 临时的高并发

在游戏业务场景中,玩家的在线和时间、活动等关系很大,不同的时间在线数量可能有几倍几十倍的差别。

对于预期内的高流量,可以通过提前做好扩容来进行承载,参考《忍三的服务端优化》中的“动态扩容和缩容”。

对于非预期的瞬间高并发,可以通过排队系统将流量卡在系统外,动态扩容后再慢慢的进入游戏中。

2.6 战斗场景中的高并发

游戏还有一个特殊的高并发场景,就是MMO的大规模玩家在某场景聚集,比如国战。

这种场景没有完美的解决方案,只能尽量的提高承载量,常见的提高方案有:

水风:游戏的数值系统的实现和演化

游戏服务端高并发和高可用,怎样支持百万玩家同时在线,不出问题

三、高可用

高可用追求系统在运行过程中尽量少地出现系统服务不可用的情况。

评价指标是服务在一个周期内的可用时间(SLA, Service Level Agreement),计算公式为服务可用性=(服务周期总分钟数 - 服务不可用分钟数)/服务周期总分钟数×100% 。

一般从两个维度进行评价:1.系统的完全可用:所有服务对于所有用户都是可用的。2.系统的整体可用:部分服务或者部分用户不可用,但系统整体可用。

高可用的目标是争取系统的完全可用,保证整体可用。

大集群下的异常

由于机器故障、网络卡顿或断线等客观存在的小概率异常情况,服务端也需要考虑这些问题,尤其是在大集群场景下,小概率事件累加变成了大概率事件,因此在大集群服务器场景下,高可用是我们必须要考虑的问题。

高可用,其实就是对各种异常状况的隔离和处理,不让小概率异常事件影响游戏的整体服务。

常见的异常有以下几种:

3.1 基于水平扩展实现高可用

上文中我们提到了水平扩展可以提高并发承载量,同时可以提高可用性,但侧重点不同。对于高并发,水平扩展表示我们可以通过增加机器/进程提高承载量。对于高可用,是说当机器/进程出现异常或者崩溃时,不会影响集群的整体可用。

在上文水平扩展中已经介绍,对于支持水平扩展的服务,有状态服务出现异常只会影响到此进程所提供的服务,其他进程正常运行;对于无状态服务,影响更小,只会影响到正在执行的流程。

当然,这需要我们写一些处理逻辑,包括:

如何实现上述逻辑其实挺复杂的,这里不详细介绍了。

服务隔离和灰度发布

开发过程中,我们应该将大功能尽量的拆成一个个小的服务,每个服务只负责一小块功能。Skynet也提供了比较好的Service模式,不同的Service可以放在一个进程中,也可以放在不同的进程中。

前文已经介绍服务隔离和灰度发布,也是为了将高风险的服务进行隔离,让它即使出现了问题也不要影响到系统的整体可用。

3.2 主从复制

对于有状态服务,支持水平扩展的进程可以做到一个进程出现异常不影响其他进程提供服务,但这个进程crash了会导致这个进程提供的服务不可用,并且造成内存中的数据丢失等问题。

为了解决这个,常见的方案是主从复制。主从复制在数据库中非常常见,是保证数据库高可用性的常见方案。

主从复制就是在主节点(master)后面挂一个或者多个从节点(slave),主节点实时地将状态/数据复制到从节点。平时是主节点提供服务,当主节点出现问题时,从节点变成主节点,继续提供服务。因为主节点近乎事实的将数据复制到从节点,可以近似保证数据不丢失。

因此,如果想进一步地提升有状态服务或者单点服务的可用性,可以使用主从复制的方案。

游戏服务器使用此方案写业务逻辑的较少,有些集群管理节点(非业务逻辑)会使用此方案。

此外,因为常见的db(mysql/mongo/redis)都自带主从复制,所以无状态服务其实也是将状态让db帮我们管理,从而获得db主从复制带来的数据不会丢失的能力。

3.3 云服务的异常处理

除了ECS机器,我们大量使用了阿里云的各类SAAS服务,比如redis/Mysql/Mongo等DB,也有类似于ELK的日志服务等。

这些服务大部分都支持主从切换等高可用方案,但我们需要考虑当他们进行主从切换时对我们系统产生的影响。

在Mysql和Redis中,当发生主从切换或gateproxy宕机,会导致网络连接断线,因此,我们必须在逻辑中处理网络中断并重连。在网络断线重连阶段,必然导致某些db请求失败,我们也需要处理这种异常问题。

在数据落地场景中,需要判断每次db请求是否成功,若不成功进行重试并且要保证请求的幂等性以防止请求多次执行。

四、高并发和高可用的目标

为了实现高并发和高可用本身具有较大的开发成本,在大部分项目中人力资源也不是无限的。所以大家在做相关工作的时候,也要过度设计,综合考虑业务需求、承载预期和开发成本。

其实我说的开发成本不仅是说程序开发量更大,更多的是越复杂的系统越容易出现问题,如果没有足够的人力去测试、维护和迭代,还不如用更简单的方式实现,反而出问题的概率更低。

当然,如果你的项目组是王者荣耀和和平精英,高并发和高可用要求非常高,人力近乎无限,请忽略此段。

百万同时在线

在游戏行业中,一般将百万同时在线作为游戏服务端架构的并发目标,百万的数量级也是绝大部分游戏(除了王者和吃鸡)的上限。

所以,在游戏前期架构设计、规划和压测中,我们可以按照百万同时在线作为基准去预估不同的系统所需要的承载量,达到这个承载量就可以了。

比如我们游戏,虽然也有不少单点和性能瓶颈,但根据我们的预估,即使这些单点存在,我们也能通过加机器支持到100w的同时在线。那么,这些单点和瓶颈就在我们的预期范围内,我们就不会进一步去优化了。

如果哪天我们游戏大火需要支持千万的同时在线了,理论上也可以继续消除单点和优化性能瓶颈,但成本会大幅度增加。

高可用 != 完全可用

我们追求服务器集群的高可用,并不是要求对于所有的异常都能容灾,那是不可能的,也是不现实的。

按照skynet的思想,若某节点出现异常没有及时响应,所有访问它的请求都会堵塞而不会timeout,若节点挂了,会直接报trace。相当于skynet把集群看为一个整体,没有做容错机制,若某个核心节点挂了,就应该集群整体不可用。

我上文说过,整体上我认可skynet的思想,可以有效地降低业务开发时的思想负担。在这个基础上,对于某些常见的异常,应该尽量降低影响,避免雪崩效应。

技术以外的高并发和高可用

高并发和高可用,和非技术也有较大的关系,比如运维能力、硬件情况,人员素质、管理水平等。

要解决高并发,需要部署大规模集群,就会对对运维能力提出较高的要求。在用户量小集群小的情况下,人工运维是可以接受的,但是随着集群规模和复杂性的增加,人工运维会变得越来越难,必须要工具化和自动化。

而很多游戏系统出现的问题,其实是由运维工作、流程和规范等问题导致的,比如战双上线后运营误发福利。

所以,为了获得高并发和高可用,运维工具、运维流程和规范一定要做好,不要人力运维,要做到运维的工具化和自动化。此外,监控、报警、人员的快速反应也是大规模系统稳定性运行的必要条件。

希望可以对大家学习和使用高并发有帮助,喜欢的小伙伴可以帮忙转发+关注,感谢大家!

上一篇下一篇

猜你喜欢

热点阅读