跨服思考
背景
随着国内手游日益火爆,玩家数量日益增加。为了更好的扩展和提供良好的游戏体验,许多手游都是通过将玩家进行分区。每一个区由一台或数台机器构成集群来提供服务。以前,各个区一般都是相互平行。但这往往会导致许多问题。例如,某些区因为人数较少,玩家无法体验到足够的多人竞技乐趣,慢慢转移到其他区,或者直接流失。导致鬼服。所以现在许多手游往往都提供跨服活动或者跨服游戏区域来使整个游戏的玩家都有机会一起竞技。
一般玩家日常活动的服称为游戏服,提供了许多单人玩法和单服竞技玩法。而跨服则通过添加额外的节点来统筹协作。
架构
一般跨服活动可以有两种架构。一种是多连接。例如游戏准备实现一个类王者荣耀的竞技活动。全服玩家可以匹配组成10人房间来战斗。可以这样实现,在原有的客户端连接服务端的基础上,再维护一条从客户端到房间服务器的链接。这种架构能提供良好的性能,尤其适合王者荣耀这种网络要求很高的游戏。同时,单服代码和跨服活动代码可以分开。但这种架构对服务器的数据的一致性有很高的要求。如何同步数据需要严谨。对客户端的要求也很高。
第二种就是类互联网的微服务架构。可以添加一个额外节点(或者设置某个节点)作为一个主服务器。主服务器主要实现两个功能,服务发现和数据同步。游戏信息可以通过游戏服和跨服节点的各个机器的网络沟通来实现。这种架构比较常见。因为首先不破坏原有的代码结构。这是最重要的一个原因。游戏的迭代非常的快。任何游戏的成功都是踩在无数前辈的尸骸上,游戏行业的代码复用程度是最高的。基本一个新游戏都是在老游戏上改出来的。而且部署简单,基本所有游戏都是同一份代码。其次,国内许多游戏目前对网络性能要求和即时性要求基本不高。允许通过牺牲部分性能来减少重构。重构往往意味着不确定性,而游戏代码要求稳定压倒一切。
设计思路
业务中运用过第二种设计架构。需求如下,玩家将参与一个跨服的大乱斗活动。活动期间,玩家将被分配到不同的房间进行战斗。这期间,玩家会不断击杀对手或者被对手击溃,击溃后会根据某种规则复活。需要统计杀人数和复活次数来集合玩家的属性计算出战力数据,用来一轮战斗结束后,将玩家分别分配到新的房间。尽可能在活动期间让对战的玩家双方战力相等。同时,活动结束时,杀人最多的玩家可以获得奖励。排行榜将尽可能展示数据给全服玩家。活动结束时,排行榜的前10可以获得珍贵的皮肤奖励。主服首先会在各个子服创建一个或复数个房间,把各个房间进行服务注册。接下来,根据战力和活动中的战斗结果来进行玩家之间的匹配,并分配到不同的房间。
这里会有两种不同的考量。
玩家的活动期间数据如果不受其他房间玩家的影响(例如英雄联盟服务器上的两个对局铁定不会相互影响),可以在本地NoSQL落地后异步复制到主服。这属于多主架构,这里的数据被隐形分区到各个子服务器,并且每个分区的领导独立。符合数据的写的线性一致。读写则为顺序一致。主服此时相当于一个复制而已。当玩家在子服务器中的战斗结束,并需要重新匹配时,只需要从原来子服务器删除数据。并且在匹配到的新房间所在的子服务器从主服务器拉取数据就可以了。
如果房间内的玩家的活动数据会受到其他房间玩家的影响,那么所有的数据都需要异步复制到主服务器进行落地后。再根据需求查看是否需要异步复制到其他子服务器。此时属于单主结构,且数据仅被主服务器修改。数据的写也符合线性一致。
以上的两个设计也符合共识算法。
- 无论是子服还是主服,针对一个玩家的活动数据,都是相同的。因为确保了永远只有一个服务器会修改数据。
- 活动数据一旦落地成功,它都会异步复制给其他服务器的NoSQL数据库。而这不会被拒绝。
- 无论是哪个玩家的活动数据,它铁定是某个服务器计算出来的,而不是凭空产生的。
因此上述的分布式架构是简单且安全的。
缺陷
上述的架构还缺少讨论一个问题。那就是如果某个服务器崩溃了,其他未崩溃的服务器能否做出正常的应答。如主服务器崩溃了,选举出新的主服务器。子服务器崩溃了,将其注册的服务移除,并分流玩家。这种取决于主程的考量。