「点击上方“GameLook”↑↑↑即可订阅微信」
对于所有网游玩家和开发者来说,发售日都是一场“大考”。通常,无论是通过营销还是口碑,很多网游在发售日都会迎来大量玩家涌入,而如果开发者对此准备不足,很容易“翻车”,导致服务器崩溃、大量玩家离开。
对于网络游戏开发商来说,如何才能确保游戏发行成功?Pragma Platform Inc. CTO Chris Cobb 在GDC 2022演讲中分享了游戏发行当天常见的10个陷阱,并给出了相应的解决方案。
以下为Gamelook音频翻译的全部内容:
克里斯·科布:
今天,我们将分享在发布日导致服务器崩溃的 10 种方法。我是 Chris Cobb,Pragma 的首席技术官,该平台提供跨平台帐户登录、游戏匹配等在线游戏服务。
我们曾与《英雄联盟》、《命运2》等全球顶级游戏以及《植物大战僵尸》等移动平台合作过。今天我们主要讨论游戏发布当天可能出现的问题。
很多人都遇到过这样的问题,这些问题大家非常熟悉,无论是开发者还是玩家,在游戏发布当天都会遇到服务器崩溃或者无法进入游戏的情况,通常这些问题都是后端支持平台导致的,这个问题之所以很重要,是因为当玩家在发布当天遇到服务器问题时,第二天能否重新进入游戏是未知数。
很难得到确切的数字,但至少有 60%-70% 的玩家如果在发布当天无法玩游戏,就不会再玩这款游戏,因此发布当天的服务器稳定性非常重要。因此,我们基本上会分享一些出错的地方网络游戏开发,然后分析我们可以做些什么。
网络游戏最重要的话题
对于网络游戏来说,最重要的是避免灾难性的错误。第一个主题是尽早开始。如果你把问题留到开发后期,可能需要几个月的时间才能解决。如果你等到开发的最后几个月才测试多人模式,显然行不通。有时一个项目的开发可能会持续数年,所以在开发之初就解决问题要比等到最后期限临近时再解决要好得多。
如果您的工作室正在转向在线服务,例如季节性活动、战斗通行证和每周发布更新,那么您遇到的问题与您每年只遇到一次的 DLC 问题不同。对于像 Destiny 或 Rocket League 这样经常更新内容的在线游戏,您不能简单地将玩家数据存储在服务器上,修补游戏,然后走开。
你需要考虑的是如何随着时间的推移不断优化数据。在不同版本之间快速迁移数据也是一个挑战。幸运的是,你可以在游戏上线前进行测试,否则玩家将成为你的第一批测试者。这时,我们会看到玩家失去他们已经获得的排名数据和内容。例如,在补丁之后,玩家的帐户甚至会被删除,所以尽早开始吧。
第二个话题是测量。如果无法观察到问题,就无法测量和解决问题,所以我们需要收集相关功能的数据。通常这分为两个方面,包括整体和线上运营,比如服务是否能跑起来,有没有必备的技术,但我们也需要关注游戏的健康程度,比如玩家如何与游戏互动,所以数据是我们发现游戏运营问题的方法之一。
我加入的团队正值一个比较大功能的发布中途,他们加班到凌晨 3 点。第二天早上,我随口问了一下这个功能表现如何,其他团队成员有些疑惑,然后回答说应该没问题,目前还没有玩家公开批评这个功能。作为职业选手,这个结果是不可接受的。如果等到玩家开始批评时才发现问题就太晚了。
还有一点是,不要独自解决这个问题。经验是最好的老师,如果你能找到在发布当天遇到服务器问题的同行,这可能会对你有所帮助。
发布日导致服务器崩溃的 10 种方法
第一步不是创建登录序列
游戏发布当天会迎来玩家数量最多的一天,可能是平均玩家数量的 10 倍甚至更多,所以这就是流量问题。你的问题不是解决正常问题,而是当所有人同时登录时该怎么办。比如在洛杉矶市中心,如果你晚上 11 点开车,还没什么问题,但如果是下午 5-7 点,那就是一场噩梦,对游戏来说就是一场灾难。
第一个例子其实是 Niantic 的 Pokémon Go,他们在发布当天多次公开分享服务器问题,我在街上玩这款游戏时也经历了数十次重新加载。他们已经发布了在线游戏,Ingress 也取得了不错的成功,但 Pokémon Go 的用户数量是前一款游戏峰值用户的 50 倍。
人们喜欢捕捉口袋妖怪,这款游戏在发布当天的玩家数量是之前游戏的 50 倍,但问题是他们没有预料到会有这么多玩家,所以他们没有登录顺序,所以他们不知道有多少人可以同时玩。他们对此没有做足够的计划,当一组玩家离开时,另一组玩家会加入,所以服务器问题不断发生。
因此,如果预计发售日玩家数量会很多,最好制定登录顺序,以保证玩家的游戏体验。我们的目标是保障发售日的顺利进行,因此必须为预计的用户数量准备足够的支持,并为此制定登录顺序,但这也带来了第二个问题。
第 2 步:创建错误的登录序列
我不知道为什么,但我认为大多数登录序列在一开始就有问题。我们构建了它们,但它们总是因为不同的原因而中断,就像我们去机场安检时,总是会遇到我们不想看到的长队一样。
很多时候,我们遇到问题,为此创建了登录序列,但上线后还是失败了。这是什么原因呢?在设计登录序列的时候,我们考虑到必须限制玩家登录,有时候可能是为了封禁垃圾账号,包括空白账号。大家总会遇到各种各样的现象。
作为开发者,我们在做登录序列的时候,会考虑需要加什么条件来识别真正的玩家,就像参加GDC需要先排队才能进门一样,这个是不可行的。我不会展示背后的原因,因为它是一项复杂的技术,但它会支撑一些问题。
你首先需要计算一次允许进入游戏的人数。例如,如果你一次只能支持 100,000 名玩家,那么你需要设计一个登录顺序来保护玩家体验,就像计算 CCU 一样。另一个容易被忽视的问题是,你忘记计算允许他们进入游戏所需的时间,就像迪士尼乐园早上开放一样,你会遇到很多人,但你不能让所有人一次进去。
因此在设计登录序列时,需要设计同时进入游戏的玩家数量,还需要了解玩家的总范围。因此,我们需要限制同时玩游戏的用户总数,以保持玩家的顺利登录。所以在授权玩家登录之前,你需要了解这个服务的用途。
步骤3:平台无负载测试
当然,你可能已经做过负载测试,也就是在发布当天与真实玩家一起测试,但这些测试人员并不是最友好的。我记得一家大型发行商在公开测试周末后发了一篇博客,说他们的服务器不断崩溃,一位好朋友问他们为什么没有足够的服务器来支持这么多玩家?这是一个很难回答的尴尬问题。
为了避免重复,简单说一下,你通常会在发布当天遇到玩家数量较多的情况,所以你的服务器无法满足当时的用户需求,一旦有用户离开,你准备的服务器可能就不够了。
这是一个复杂的问题。你的目标是在发布当天了解真实的用户压力。我们的目标当然是证明可以支持多少玩家体验游戏。一位行业专家曾经说过,所有模型都是错误的,但有些模型是有用的。
所以,负载测试的目的是找到一个有用的模式,这里有几种情况,第一种是负载测试是发布当天的实际用户数,最完美的方式是用真实的玩家数来做负载测试,然后有针对性地发现问题、解决问题,但不能提前做。
因此,你需要模拟玩家在游戏发布当天进入游戏的场景,他们去哪里,他们在商店里浏览什么,他们经历了哪些游戏。你实际上想要模拟真实用户在游戏中的行为,然后在复杂的测试中将其扩大规模。
我们无法通过单个 API 请求来模拟它并将其应用于拥有 500,000 名用户的场景。你想要构建的是一个有用的模型,表明玩家可能如何行动,而不是假设他们都做同样的事情。
另一个是负载测试环境,它必须适应你的开发环境,包括安全问题、资源等各方面。你需要提供数据库备份、硬件准备,一切都需要为负载测试做好准备。但我们该怎么做呢?你希望基础设施像写代码一样简单,但实际上每个基础设施都有不同的菜单和控制台,你无法在另一个环境中完全复制它。
您可能会遇到这样的情况:理论上负载测试没有问题,但由于配置原因而出现错误。您不想走捷径,因为这涉及到许多关键方面。
在开发《英雄联盟》时,我们让并行数据中心跟踪一切,甚至跟踪负载测试 2,但有时我们要做太多事情,以至于完全忘记了负载测试 2。有一天我问技术总监,负载测试 1 怎么办?但他说,负载测试 1 在哪里?最终的结果是,我们不知道数据中心在哪里丢失了,我们实际上失去了对整个环境的控制。有时事情就是这么奇怪,管理这么多环境非常具有挑战性。
所以只好重新做了第二次负载测试,浪费了很多时间。所以你必须关注每一个制作环节,就像真正的游戏运营一样。虽然我们最终解决了这个问题,但这仍然是一个警示。
目标是对生产环境进行压力测试。不要忽略测试期间对配置所做的任何更改,也不要忽视测试环境,因为它可能代表真实的玩家行为。
步骤 4:做太多问卷
这是一个很常见的问题。当我们开始对游戏进行预测时,我们经常会做一些问卷调查。这是一个非常好的开始方式。你想制作更好的 UI 并设计更好的体验,所以你会想,我们最好调用服务器以防万一出现变化。随着时间的推移,这种方法可能会出现问题,因为它就像调用调查一样。
另一个与英雄联盟相关的例子是,我们有可以向玩家发送消息的工具,如果某个服务出现故障,他们可以使用此工具了解发生了什么,因此你可以直接向某个地区的 50 万人发送消息。
但一旦我们发送了消息,我们又会收到大量的玩家请求,而且这种情况多次发生。我们想知道,到底哪里出了问题?其实功能本身没有问题,但出于某种原因,玩家收到消息后,我们的设计在 30 秒后重置了计时器,因此有很多重复的调用,突然之间,我们设计了一个 Bug,攻击了我们自己的服务。
调查现象是经常遇到的,这种情况下你需要特别与工程团队协调。你不能把它想象成只有一个客户端,然后模拟一对一的网络调用。你必须想象有 100 万个客户端,每个客户端都会进行调用。这需要你在某种程度上改变思考问题的方式。你需要避免这样的问题,因为随着游戏的发展,你会遇到大问题。
我们的建议是保持套接字打开,这样你的服务器就可以直接向用户发送信息,这样可以提供非常有效的反馈。每次出现问题时,玩家都会知道发生了什么,但你不需要每隔几秒钟就调用服务器来查看发生了什么变化。这样,你只需要在问题发生时解决问题,从而创建一个可扩展的平台。
因此我们在调用服务器的时候要谨慎使用。
步骤 5:无数据库分片
如果要我说希望大家从今天的分享中得到什么,我认为这是最需要关注的。不知道为什么,很多工程师都不愿意谈数据库分片的问题。传统上,对于很多中小型团队来说,我们没有技术和资源来做数据库分片,但现在支持新硬件比以往任何时候都要容易,所以我鼓励同事们在研发中尽早考虑这个问题。
单一的数据库会给你带来非常大的瓶颈,即使网络服务器扩大了,如果大家都同时使用数据库,也会给你的系统带来很大的压力。这是需要注意的一个非常重要的问题,也是最容易导致游戏崩溃的原因。比如除了前面提到的玩家账号问题,用户背包也会给你带来问题。
解决这个问题的方法有很多。其中一种是将数据库分片。例如,将数据库分成四个部分,每个部分存储 25% 的玩家数据。这是一个很好的开始方法。另一种方法是将数据库中的数据分开。如果您的游戏有背包、进度、任务和目标,以及 Battle Pass、季票等,那么使用 Jsonclub 来存储关键数据就很容易了。
在项目开始时这样做是没问题的,因为你需要存储的数据较少,即使内部测试扩展到 100 人,这种方法仍然有效。然而,在发布当天,这可能行不通,即使只有与测试期间相同的用户数量,将他们分成几组也是有用的。
例如,网络更擅长传输大量小量数据,而不是大量数据。另外,如果你将背包与充值分开,这意味着你每次需要发送的数据要少得多。当玩家购买某样东西时,你只需要处理背包,而不需要处理很多没有变化的东西。
Pragma 合作的客户很多都是大型游戏开发商,他们通常与发行商有协议,发行当天会有数十万甚至数百万用户登录,而我们只有几个月的时间来解决所有问题。好消息是他们都有登录序列,他们有负载测试,这都很好,但他们的负载测试登录序列的上限是 4 万人,他们在这个问题上卡了很久。
通过和他们的架构师聊天,我们发现了两个问题网络游戏开发,第一,他们为所有服务器设计了一个数据库服务器,包括账号、登录、玩家数据等,所有数据都塞在一个巨大的块里。
因此,上线当天就有 30 万用户登录,上线一周就有 100 万玩家。经过多方考虑,我们决定在工程上只做分库,第一步就是拆分数据库,光是这个措施就保证了制作过程中 22 万玩家可以稳定体验游戏,再加上登录顺序授权,这个问题很快就解决了。
总之,您需要对数据进行分段以实现可扩展性,这可以从根本上改变平台的特性。
步骤 6:微服务太多
这可能和单子上所有的问题都不一样,尤其是在大公司,团队一个部门的一个bug可能连累到所有人,所以你需要解决所有的问题,很麻烦。所以让一个团队专门解决某些问题会更有效率。
但是如果走得太远,这个方法可能就不行了。首先你要知道如果调用其他服务器失败了怎么办?我们之前的想法是,所有服务器都是独立的,每个服务器都可以响应其他服务器的请求。就算其他服务器没有响应,你也可以继续自己的服务。
但问题是,如果你的调用失败了怎么办?在游戏中,我们其实只有一个基本的生态系统供玩家体验。一般来说,你不会控制发送到其他服务器的无效请求。你只想获取服务所需的数据,因此不同的服务会相互影响。如果你以一刀切的方式管理太多的微服务,就会让问题变得复杂。通常,如果一个关键服务失败,也会影响其他服务。
这会造成一场噩梦,每个服务都会优先考虑如何让他们的服务运行,从而导致大量不同的环境,我遇到过这样的情况,一个朋友说服务器停机不是他的错,而是其他服务导致游戏崩溃,每个服务都给出了相同的解释。
这很有趣,但我们都应该知道我们的服务是如何工作的,而过多的微服务经常发生的情况是,如果一个服务失败,就会产生连锁反应。我们希望保持规模,但同时又要有清晰度,并且需要在不同的服务之间划一条分界线,这样你就能很容易地看到问题发生在哪里。
一般来说5-10个服务就会让你的团队精疲力尽,如果超过20个服务,你会发现很多东西都找不到了。
步骤 7:无数据缓存
数据库是一个很有价值的东西,但是制作数据库比很多其他方面都要慢,缓存就是把我们的数据放到一些更快的位置,让它更容易获取。
当我们与第三方网站合作时,他们在粉丝网站上创建大量测试账户是很常见的。这些账户是机器人,它们试图获取玩家数据。基本上,我们认为这是件好事。但问题是,每隔几个月我们就会遇到问题,尤其是当新平台进来时。如果所有存储空间都被这些测试账户占用,那么真正的用户可能会遇到问题。
然后,我们专门为第三方服务构建了一个 API,以便为所有这些玩家数据制作缓存副本,这样第三方服务就可以自己迭代这些数据,而且它们的变化非常缓慢。这让我们避免了服务器崩溃,最终让我们和所有第三方都非常高兴。
我们想要保护我们的数据库,但这又引出了下一个问题:
步骤 8:使用过多缓存
正如我们刚刚讨论的那样,缓存可能是一个很好的工具,但有时我们会用得过多。如果缓存了太多数据,您甚至不知道某些数据是否已过期,而唯一知道的方法就是返回数据库进行验证。但是通过优化太多东西,您将逐渐失去对数据来源的控制,从而使您的团队很难在不引入错误的情况下进行更改。
在与一家工作室合作时,在连接到我们的服务之前一切都运行良好,但是连接到我们的服务后,他们的服务器崩溃了,同时还关闭了一些关键服务,例如支付处理,这非常奇怪,因为我们的服务与支付无关。
经过我们的调查,我们的服务每秒只发送 10-15 个请求,对于这样的游戏来说,这个数量并不算多。经过进一步调查,我们发现他们的很多服务其实没必要这么大,大量的缓存占用了很大的流量。在这个案例中,我们共用了支付基础设施服务。正是这两个不相关的服务给我们带来了很大的痛苦。
我们跟他们的团队沟通,比如为什么有这么多服务,他们给出的理由是准备扩容。深入调研之后发现,这个团队做的事情太多了,他们自己都无法全部追踪到,所以最后的方案是删掉很多东西,只保留 90% 的代码。
所以目标是你应该测量和评估优化方案,不要猜测,很难知道你所做的更改会产生什么影响。如果你什么都没有,最好做一些数据收集。这是一个很大的话题,但我长话短说,你应该先问自己,这个功能发布后我想要什么?通过一系列问题,找到最能回答这些问题的答案,然后有选择地收集数据。
否则你将面临大量的数据,而没有人知道这些数据是用来做什么的、从哪里来的、有什么用途,这会给后续的优化带来非常高的复杂度。
步骤 9:使用最新技术
很多人可能都知道,项目经常会延期,你的游戏可能会因为使用新技术而延期数月甚至数年。Pragma 的原则是只选择那些流行的技术。
我们曾经做过一个项目,所有的比赛都是在客户端之外的一个平台上进行的,一旦这个平台出现问题,玩家的客户端也会遇到很多问题,玩家抱怨不断。我们发现很多技术已经过时了,所以我们就想出了一个想法,用现代技术。
我们希望给玩家带来更好的体验,最初的计划是五个工程师在几个月内完成。但这个项目持续了四年,最终耗费了 200 人的团队。发布当天,游戏体验非常粗糙,有些玩家甚至无法玩游戏。在性能方面,它违背了我们最初的愿望,占用了更多的内存和 CPU。
还有就是开发者的体验,这两个系统其实用的是两种完全不同的编程语言,需要切换到一个全新的技术栈、开发环境、编程语言才能完成,这是一个非常极端的案例,成本比我们预想的要高出 100 多倍。
分享这个故事的目的是,有时用新技术取代旧技术是正确的,但你需要仔细权衡。有时新技术的成本非常高。当你在发布当天解决问题时,你必须问自己什么是当务之急。我们的建议是使用一些已被证明可扩展的技术,这对于游戏发布很重要。
比如 Pragma 使用的是 Java,而很多大公司使用的是 MySQL。也许没人会想到,到了 2022 年,很多人还在使用 90 年代的技术。但使用成熟的技术,对于降低游戏发布的风险有很大的帮助。
许多工作室的内部工具都是尝试新技术的绝佳试验场,但当您支持游戏发布时,最好使用可以让您的游戏发布更成功的成熟技术。
步骤10:所有函数嵌入两次
这里讲两个现象,一个是函数的不断重写,另一个是同一个函数在代码库中多次呈现。这样的代码库很难维护,会导致很多质量问题,也会影响服务器的扩展。我们都知道简单就是最好的,但有时候,过于追求简单反而会很难。你想摆脱一切不必要的东西,实现极致的简单,但你也要考虑自己是否有时间这么做。
我们谈到了重写,现在让我们谈谈重复代码。这个案例与英雄联盟有关,特别是邀请好友功能,我们添加了验证,例如您是否拥有该游戏,您邀请的人是否玩过英雄联盟,基本上,代码是从以前的平台复制粘贴的,当我加入时,我发现很多验证都失控了,这不是一件好事,因为它会引入错误并且不利于跟踪。
后来我们发现这段代码加了太多东西,比如玩家的背包。这个游戏有 10 个玩家,假设每个玩家有 50 个背包位,这就意味着代码需要验证很多东西才能得到三行代码就能得到的结果。最后我们简化为检查玩家的背包,找到东西,然后继续。
这里的目标是不要重复自己,这样更容易做出改变,并且在扩展时可以提供更多帮助。这很难做到,但值得。