游戏开发经验谈(一):游戏架构里隐藏的五个坑及其应对方案 | U刻
  • 游戏开发经验谈(一):游戏架构里隐藏的五个坑及其应对方案

    栏目:技术分享

    随着移动终端硬件配置的飞速提升,手游行业开始从爆发期进入相对稳定的发展期。残酷市场竞争环境下,游戏公司纷纷寻求业务创新,手游重度化、VR/AR游戏、经典IP回归之外,游戏出海和全球服也成为新亮点。这也意味着云服务需要承载越来越多后端服务器的支撑工作,合理的平台架构将成为系统稳定运行的基础保障。

    迄今为止,UCloud平台支持的游戏已经超过了1000余款,其中手游占据了70%以上。在这过程中我们也陪用户踩过了很多“坑”,本文将结合以往的一些经验和成功案例,为大家阐释游戏架构设计上,可能会遇到的一些问题和解决方案。

    场景A:All In One的MudOS架构

    MudOS是第一代游戏架构,目前应该是无人使用的历史里程碑了,之所以会在这里提到,是因为在云平台,仍然有不少用户使用数据、计算、日志全部集中于同一台服务器的All In One集中式部署架构。

    以当前的技术来说,公有云还没能完全避免宕机对业务造成的影响,而宕机必然要导致业务一段时间内的不可用。一旦出现云主机内部系统崩溃,对于这种架构的服务器更是灾难性的。因为时间和数据都很难保证,最终可能必须通过备份文件才能进行回档。

    此外,集中式部署架构对于云主机的性能要求非常高,随着业务的增长,开发者经常要重新调整配置,甚至最后直接购买物理云主机。同时,为了达到过高的性能要求,需要对云产品的硬件灵活性和弹性伸缩能力进行取舍,即使在购买了物理云主机的情况下,云平台的成本优化效果也无法达到最大化。因此,希望大家在游戏设计中规避掉这种集中式部署架构,尽量使用逻辑服或者微服务的模式。

    场景B:疯狂掉线

    掉线对所有游戏玩家来说都是非常痛苦的事情,我们曾经手过一个疯狂掉线的案例,这个案例的独特之处在于玩家在游戏过程中很少发生卡顿和瞬移问题,但是会经常出现掉线现象,而且还是不定期的玩家集中掉线。掉线的时间点也非常巧合,基本上是在机房监控到DDoS、或者地方级以上骨干容灾切换闪断的时候。

    我们初步分析可能是业务和网络的特殊情况触发的,在和用户交流业务逻辑之后,了解到用户的游戏设计采用的是TCP逻辑:业务1分钟发出1个心跳包,如果30秒未收到ACK测试则认为客户端掉线需要重连。很明显,这种设计并没有考虑到丢包或错包等问题。

    因为实际情况下,全球运营商的网络设备都有一定的错包率或丢包率,1分钟1个心跳包模式下,一旦发生丢包,玩家在1分30秒内无法收到测试信息,必然会被系统剔除,导致掉线。而在容灾切换或者DDoS情况下,丢包或者错包的问题会更加严重,玩家会集中掉线也就可以解释了。

    定位问题后,我们帮助用户对以上逻辑进行了修改,将玩家的掉线时间从1分30秒收敛成30秒,设置业务每10秒3个心跳包,超过3个周期未收到则视为掉线。每10秒3个心跳包的情况下,超过30秒就有9个心跳包,只有当这9个心跳包全部丢失,系统才会认为玩家离线。逻辑修改后会形式一个缓冲区,避免错包或丢包情况下造成的系统判断失误。

    场景C:单点DB的危机

    下图的业务架构设计得已经相对完整,整个系统采用的是DB的主从架构,可能宕机造成的风险都已经规避,唯一的疏漏在于用户将Cache和业务绑定,一旦业务重启,整个Cache就会被清空,同时如果Cache达到上限也造成业务异常。

    有一次用户的DB磁盘异常需要较长时间恢复,雪上加霜的是Cache即将写满,因为更改数据库指向必须重启业务,为了保证游戏的正常运行,又不能把业务切到从库。最后只好联合当时的DBA、内核以及系统专家,耗费大量时间来恢复主库。

    为了避免这种情况再次发生,后续用户直接将Cache层拆分出来放到我们的高可用Redis上来保证系统的稳定。

    场景D:Redis崩溃

    相信做游戏开发的人或多或少都经历过Redis崩溃问题。本案例中,用户采用了比较前沿的框架,它抛弃了传统数据库,直接使用内存存储作为数据的唯一存储器。全球服上使用的是微服务框架,不存在单点风险,业务能力非常强。但因为在研发过程中第一次使用集群化,所以也踩过一些坑。

    • 问题一:Redis AOF造成短暂查询堆积。解决方案是进行分片操作,保证AOF时间不一样,将整体业务查询危机缩减下来。此外,针对游戏框架申请DPA,尽量减少刷数据的可能性。
    • 问题二:QPS极限仅能达到数千,甚至出现了不定期慢查询卡死的情况。查看代码和数据时发现,用户的业务语句中大量使用了KEYS命令且无任何限制,这就类似于在巨大的MySQL集群里select *,解决方式是直接将所有KEYS风险语句进行调整和范围限制,保证业务的正常运作。

    另外,集群Redis是基于proxy和Redis分片实现的,而非集群的原生Redis对短连接的处理性能极差,并且由于单线程的特性,非常容易因为短连接将CPU打满。对于Redis来说,即使提供最强的44核CPU,最后程序运行的结果也是1核跑满,其它43个核围观。

    因此,在设计游戏的时候,使用Redis要特别注意两点:1、集群Redis尽量少用Keys命令;2、主备Redis尽量不要使用短连接,因为短连接过多会造成整体业务性下降,尤其在Redis特别集中的环境下,影响会非常严重。

    场景E:Register Server 单点

    下图为一个实时对战场景的全球服架构,架构采用了自动注册机制,注册服务器类似路由表功能,会保存所有微服务集群的节点IP信息以供业务节点需要时查询调用。架构左侧上层为高可用数据库、高可用内存存储,下方是对战服务器和平台入口,右侧为工会聊天室,框架里面接入了四个对战服。这个框架的稳定性和扩展性都非常强,主机状态对整体业务的影响极小。

    整个框架美中不足的是,最核心的注册服务器采用的是单点,且该服务器串行在整个业务的逻辑中,一旦服务器异常,同样会造成整体业务不可用。如果不做任何修改,在后续上线运维的过程中,不论是因为压力、系统还是其它原因,Register服务器都将会是一个巨大的技术风险。

    针对上述问题,我们根据不同的场景推荐了两种不同的解决方案:

    • 管控分离,通过中心推送+本地缓存机制旁路Register。这种方案适用于调用逻辑较为简单的情况;
    • 影子备份,配置备用Register Server同步数据并切换。这种方案适用于注册逻辑较为复杂的情况。

    总结

    本文主要简单介绍了不同场景、不同游戏案例当中遇到的一些框架设计问题和解决方案。下篇文章,将会以对战类全球服游戏为例,重点讲讲游戏架构的设计思路与技术实现。

    作者介绍

    沈皓:UCloud PathX产品早期方案设计者之一,深耕全球服游戏领域,曾全面负责多个知名游戏的全球/跨国业务对接、部署及落地。对于MOBA、RTS、FPS等各类游戏的出海全球化的需求、难点、架构实现等有独到见解。

    5