两位工程师如何为数亿玩家解决实时持久化事件问题。
译自 Inside Supercell’s Minimalist Massive Social Network,作者 Cynthia Dunlop。
仅凭两位工程师,Supercell就承担了将基本账户系统扩展为连接数亿玩家的社交平台的艰巨任务。账户管理、好友请求、跨游戏推广、聊天、玩家状态追踪和团队组建——所有这些都必须在Supercell的五大游戏中运行。团队希望所有这些都能由一个单一的解决方案来覆盖,这个解决方案足够简单,一位工程师就能维护,同时又足够强大,能够实时处理海量需求。
Supercell的服务器工程师Edvard Fagerholm最近分享了两人团队如何应对这项任务。他解释了团队如何将简单的账户管理工具转变为全面的跨游戏社交网络基础设施,该基础设施优先考虑操作简便性和高性能。
注意:如果您喜欢听到这样的工程壮举,请加入我们的Monster Scale Summit(免费+虚拟)。来自Disney+/Hulu、Netflix、American Express、Slack、Salesforce、Atlassian等公司的工程师将分享策略和案例研究。*
Supercell是一家总部位于芬兰的公司,旗下拥有热门游戏“Hay Day”、“部落冲突”、“海岛奇兵”、“皇室战争”和“荒野乱斗”。这些游戏每款的终身收入都超过了10亿美元。
不知何故,该公司设法以极少的员工实现了这一点。直到最近,所有为数亿月活跃用户服务的游戏的账户管理功能都是由两位工程师构建和管理的。这也就是Supercell ID的由来。
Supercell ID最初是一个基本的账户系统——帮助用户找回账户并将账户转移到新设备。它最初实现为一个相对简单的HTTP API。
Fagerholm解释说:“客户端可以对账户API执行HTTP查询,该API主要返回客户端可以向游戏服务器展示以证明其身份的签名令牌。一些操作,例如发出好友请求,需要账户API向另一位玩家发送通知。例如,‘您是否批准此好友请求?’为此,有一个用于通知的事件队列。我们会将事件发布到那里,游戏后端将使用游戏套接字将通知转发给客户端。”
在2020年末加入Supercell ID项目后,Fagerholm开始负责通知后端的工作,主要是针对Supercell五大游戏的跨推广。他很快意识到他们需要自己实现双向通信,并按如下方式构建:
客户端连接到一群代理服务器,然后路由机制将事件直接推送到客户端(无需经过游戏)。这足以满足处理跨推广和好友请求的直接目标。它相当简单,不需要支持高吞吐量或低延迟。
但这让他们想得更远。团队意识到他们可以使用双向通信来显著扩大Supercell ID系统的范围。
Fagerholm解释说:“基本上,它允许我们实现以前属于游戏服务器的功能。我们的目标是将任何正在开发的新游戏可能需要的功能打包到我们的系统中,从而加快它们的开发速度。”
就这样,Supercell ID开始转变为一个跨游戏社交网络,支持好友关系图、组队、聊天和好友状态追踪等功能。
此时,后端的社交网络方面仍然是一个人的项目,因此团队在设计时考虑到了简洁性。引入了抽象。
“我们希望只有一个简单的抽象来支持我们所有的用途,因此可以由一位工程师设计和实现,”Fagerholm解释说。“换句话说,我们不想构建聊天系统、状态系统等。我们想构建一个东西,而不是很多。”
找到合适的抽象至关重要。分层键值存储与变更数据捕获完美地满足了这一要求。以下是团队的实现方式:
- 键值存储中的顶级键是可以订阅的主题。
- 每个顶级键下面都有一个两层的映射——映射(字符串,映射(字符串,字符串))。对顶级键下数据的任何更改都会广播给该键的所有订阅者。
- 最内层映射中的值也带有时间戳。每个数据源控制自己的时间戳,并定义正确的顺序。客户端会丢弃任何比它已经存储的更旧的时间戳的更新。
Fagerholm需要一个数据库,这个数据库要能够支持项目的技术支持需求,并且不需要他们两人工程师团队不断地进行维护。这转化为以下标准:
- 处理许多小写入,低延迟;
- 支持层次化数据模型;
- 作为服务管理备份和集群操作。
ScyllaDB Cloud 非常适合我们的需求。ScyllaDB Cloud 是 ScyllaDB 的完全托管版本,ScyllaDB 是一个以在大规模情况下提供可预测的低延迟而闻名的数据库。
要了解在Supercell游戏中是如何实现的,让我们来看两个例子。
首先,考虑聊天消息。一个简单的聊天消息可能在数据模型中如下表示:
<room ID> -> <timestamp_uuid> -> message -> “hi there”
metadata -> …
reactions -> …
法格霍尔姆解释说:“订阅的最高级别键是聊天室ID。下一级键是时间戳-UID(唯一标识符),因此我们对每条消息都有排序,并且可以查询聊天记录。内部映射包含实际的消息以及附加的其他数据。”
接下来,我们来看“存在感”,这在Supercell的新(且备受期待)游戏“mo.co.”中被广泛使用。根据法格霍尔姆,存在感的目标是:“当组队战斗时,你希望实时看到朋友的头像和当前的构建——基本上是朋友的武器和装备,以及他们在做什么。如果你的朋友改变了他们的头像或构建,下线或上线,它应该立即在‘组队’菜单中可见。”
玩家的状态数据被编码进Supercell的分层映射中,如下所示:
<player ID> -> “presence” -> weapon -> sword
level -> 29
status -> in battle
注意:
- 顶级是玩家ID,第二级是类型,内部映射包含数据。
- Supercell ID不需要理解数据;它只是将其转发给游戏客户端。
- 游戏客户端不需要知道好友图,因为路由由Supercell ID处理。
让我们以 Fagerholm 提供的系统架构之旅作为结束。
后端被分割成API、代理和事件路由/存储服务器。主题存在于事件路由服务器上,并在它们之间进行分片。客户端连接到代理,代理处理客户端的主题订阅。代理将这些订阅路由到适当的事件路由服务器。端点(例如,用于聊天和在线状态)将它们的数据发送到事件路由服务器,所有事件都存储在ScyllaDB Cloud中。“
每个主题都有一个主分片和一个备份分片。如果主分片宕机,主分片会保持每个消息的内存序列号以检测丢失的消息。次分片会转发没有序列号的消息。如果主分片宕机,主分片重新启动将触发客户端状态的刷新,以及重置序列号。
路由层的API是一个简单的事后RPC,包含一批主题、类型、键、值元组。每个API的工作只是将它们的数据重写为上述元组表示。每个事件在广播给订阅者之前都会写入ScyllaDB。我们的API是同步的,这意味着如果API调用给出了成功的响应,消息就已经在ScyllaDB中持久化了。多次发送相同的事件不会有任何问题,因为对客户端应用更新是一个幂等操作,可能的例外是多个序列号映射到相同的消息。“
连接时,代理会找出你所有的好友并订阅他们的主题,你所属的聊天群组也是如此。我们还为连接的客户端订阅主题。这些用于向客户端发送通知,比如好友请求和跨推广。路由器重启会触发代理重新订阅主题。”
我们使用 Protocol Buffers 来节省带宽成本。所有负载均衡都在 TCP 层进行,以保证同一个 HTTP/2 连接上的请求由代理上的同一个 TCP socket 处理。这样我们就可以在初始监听时将某些信息缓存在内存中,因此无需在其他请求中重新获取。我们有足够的并发客户端,因此无需单独对单个 HTTP/2 请求进行负载均衡,因为流量本身就均匀分布,并且处理不同用户的请求成本大致相同。我们在代理和路由器之间使用持久性套接字。这样,我们可以轻松地每秒向单个路由器发送数万个订阅,而不会出现问题。
如果您想观看完整的技术演讲,请点击下方播放:
如果您想了解更多关于 ScyllaDB 在游戏领域的作用,您可能还想阅读:
- Epic Games: Epic Games 如何使用 ScyllaDB 作为 NVMe 和 S3 前面的二进制缓存来加速 Unreal Cloud DDC 使用的大型游戏资产的全球分发。
- Tencent Games: 腾讯游戏如何基于命令和查询职责分离 (CQRS) 和事件溯源模式,结合 Pulsar 和 ScyllaDB 构建服务架构。
- Discord: Discord 如何使用 ScyllaDB 来支持其大规模增长,从一个利基游戏平台发展成为全球最大的通信平台之一。