两名工程师如何为数亿玩家构建实时持久化事件系统
Supercell 仅凭两名工程师,就承担起将基础账户系统发展为连接数亿玩家的社交平台这一艰巨任务。账户管理、好友请求、跨游戏推广、聊天、玩家在线状态追踪和组队功能——所有这些都需在其五款主要游戏中无缝运作。他们希望采用单一解决方案,既要简洁到一名工程师即可维护,又要强大到能实时处理海量需求。
Supercell 的服务器工程师 Edvard Fagerholm 近期分享了这支两人团队如何应对这一挑战。下文将揭示他们如何将简单的账户管理工具转变为优先考虑运维简洁性与高性能的全面跨游戏社交网络基础设施。
背景:Supercell 是谁?Supercell 是一家芬兰公司,旗下热门游戏包括《Hay Day》《部落冲突》《海岛奇兵》《皇室战争》和《荒野乱斗》。每款游戏均创造了终身收入超 10 亿美元的佳绩。
令人称奇的是,他们以极少的人力实现了这一成就。直至不久前,服务数亿月活跃用户的游戏账户管理功能仅由两名工程师构建和维护。由此,Supercell ID 应运而生。
Supercell ID 的诞生Supercell ID 最初作为基础账户系统出现,旨在帮助用户恢复账户并迁移至新设备。其最初通过相对简单的 HTTP API 实现。
Edvard 解释道:“客户端可向账户 API 发起 HTTP 查询,该接口主要返回签名令牌,供客户端向游戏服务器证明身份。某些操作(如发送好友请求)需要账户 API 向其他玩家发送通知,例如‘是否同意此好友请求?’。为此我们设立了通知事件队列:将事件发布至队列后,游戏后端会通过游戏套接字将通知转发给客户端。”
双向通信的引入Edvard 于 2020 年底加入 Supercell ID 项目后,开始主要负责跨游戏推广的通知后端开发。他很快意识到需要自主实现双向通信,并构建了如下架构:
客户端连接至代理服务器集群,路由机制将事件直接推送给客户端(无需经过游戏服务器)。这满足了处理跨游戏推广和好友请求的即时需求,该方案简单且无需支持高吞吐或低延迟。
但此举启发了更宏大的构想。他们发现可利用双向通信极大扩展 Supercell ID 系统的能力范围。Edvard 表示:“这使我们能实现原本属于游戏服务器的功能。我们的目标是将新游戏开发可能需要的功能整合到系统中——从而加速开发进程。”
至此,Supercell ID 开始转型为支持好友图谱、组队、聊天及好友状态跟踪等功能的跨游戏社交网络。
Supercell ID 向跨游戏社交网络的演进此时,后端社交网络部分仍由单人开发,故设计时充分考虑了简洁性。抽象化理念由此登场。
寻找合适的抽象模型
“我们追求用单一简洁的抽象模型支撑所有应用场景,从而可由单名工程师完成设计与实现。”Edvard 解释道,“换言之,我们避免分别构建聊天系统、在线状态系统等,目标是构建统一框架而非多个独立系统。”
找到合适的抽象模型至关重要。采用变更数据捕获(CDC)的层级式键值存储完美契合需求。其实现方式如下:
- 键值存储的顶层键是可订阅的主题(topic)
- 每个顶层键下包含一个双层映射结构 map(string, map(string, string))。对顶层键下数据的任何变更都会广播给该键的所有订阅者
- 最内层映射的值均带时间戳。各数据源控制自身时间戳并定义正确顺序,客户端会丢弃时间戳早于本地存储的更新
典型数据变更如“等级从 10 变为 11”。玩家游戏过程中会触发大量此类更新,意味着持久化事件涉及海量小型写入操作。
数据库选型考量
鉴于团队规模极小,他们需要既满足技术需求又易于管理的数据库。具体标准包括:
- 低延迟处理海量小型写入
- 支持层级数据模型
- 以服务形式管理备份与集群操作
ScyllaDB Cloud 最终成为理想选择。(ScyllaDB Cloud 是全托管版本的 ScyllaDB,该数据库以在大规模场景下提供可预测的低延迟著称)。
系统运行实景通过两个示例直观展示该系统在 Supercell 游戏中的运作机制。
首先看聊天消息。简单聊天消息在其数据模型中的表示如下:
Edvard 说明:“被订阅的顶层键是聊天室 ID。次级键是时间戳-UID 组合,从而实现消息排序和历史查询。最内层映射包含消息本体及附加数据。”
其次看 Supercell 新款游戏 mo.co 中重度依赖的“在线状态”功能。Edvard 阐释其目标:“组队作战时,需实时查看好友头像与当前装备配置——包括武器装备及其动态。若好友更改头像装备、离线或上线,组队菜单应即时可见。”
玩家状态数据在 Supercell 层级映射中的编码方式如下:
注意:
- 顶层为玩家 ID,次级为类型,内层映射包含数据
- Supercell ID 无需理解数据内容,仅负责转发至游戏客户端
- 游戏客户端无需知晓好友图谱,路由功能由 Supercell ID 处理
最后让我们纵览系统架构。
“后端分为 API 层、代理层和事件路由/存储服务器。主题存在于事件路由服务器并分片存储。客户端连接至代理服务器处理主题订阅,代理将这些订阅路由至相应事件路由服务器。端点(如聊天和在线状态)将数据发送至事件路由服务器,所有事件均持久化存储在 ScyllaDB Cloud 中。
每个主题设主备分片。若主分片故障,其会维护每条消息的内存序列号以检测丢失消息。备用分片将转发无序列号的消息。主分片恢复时将触发客户端状态刷新并重置序列号。
路由层 API 是包含批量(主题、类型、键、值)元组的简单事件发布 RPC。各 API 仅需将数据重写为上述元组形式。所有事件在广播给订阅者前均写入 ScyllaDB。我们的 API 具有同步特性:若 API 调用返回成功响应,则消息已持久化至 ScyllaDB。重复发送相同事件无害,因为客户端更新操作具备幂等性。
连接时代理会识别所有好友并订阅其主题,对所属聊天群组同理。我们也会为连接中的客户端订阅主题,用于发送好友请求和跨游戏推广等通知。
路由器重启会触发代理重新订阅主题。
我们使用 Protocol Buffers 节省带宽成本。负载均衡均基于 TCP 层实现,以此确保同一 HTTP/2 连接的请求由代理的同一 TCP 套接字处理。这使我们在初始监听时可将特定信息缓存于内存,避免后续请求重复获取。由于并发客户端数量充足,且不同用户请求的处理成本相近,我们无需对单个 HTTP/2 请求单独负载均衡。代理与路由器间使用持久化套接字,从而可轻松向单一路由器每秒发送数万订阅请求。”
共同学习,写下你的评论
评论加载中...
作者其他优质文章
