任天堂 NPLN 服务架构演讲学习笔记
NPLN 是任天堂的新一代的通用多人在线游戏网络系统,旨在替代自 3DS 时代就使用的 NEX 系统,采用全面的现代架构来进行开发,目标是在玩家无感知的情况下替换掉 NEX。NPLN 自 2018 年开始开发,全面基于 Google Cloud,并于 2021 年首先在《怪物猎人:崛起》中进行使用。
截至目前,接入 NPLN 的游戏有下面三款:
- 怪物猎人:崛起
- 宝可梦传说:阿尔宙斯
- Splatoon 3
这篇文章大体上是任天堂于 2022 年 4 月,在 Google 日本主办的 Google Cloud Day: Digital ’22 活动上做的主题演讲的笔记,内容大部分来自官方 slider 的翻译和截图。这次分享也是现在网上能找到最全面的 NPLN 服务端资料(SDK 暂未全面开放给所有开发者,任天堂自己采用面也不广),故特此总结以飨读者。由于我个人日语是个二把刀,内容如果有翻译不准确的地方,还请不吝赐教。
注意,这篇文章无法回答以下问题:
为什么 Splatoon 3 联机还是这么烂?
NPLN 最大的更新是在游戏的其他在线体验上,而对局的联机方案还是跟 Splatoon 2 一样的基于 P2P 的方案。也就是说,2 代存在的问题,3 代大概仍然存在。
有种说法是 NPLN 有 UDP Relay,在 NAT 打洞失败后会 Fallback 到这种模式,由于官方演讲没有提及,我在此无法下定论。(根据数据挖掘的结果,NPLN 也许使用了基于 WebRTC 的 TURN 模式)
具体 XXX 技术是怎么实现的?
目前由于 SDK 没有开放,同时逆向分析难度较大,本文仅总结任天堂在 GCD ‘22 上公开的部分,特别细节的部分暂时不知道。
这篇文章会涉及到具体某个游戏吗?
不会。本文只包含了 NPLN 服务端的整体架构和实现分析。对于游戏怎么接入的、SDK 怎么使用的等等都不包含在内。
设计原则
NPLN 的架构是微服务化的、面向多团队的合作开发架构。
为什么使用微服务?
- 以微服务为导向的设计:游戏开发规模扩大的对策
- 需要支持的游戏服务和能力在增加
- 任天堂内部使用这套系统的开发者也在增加
- 支持多租户的设计:需要使用这套系统的作品数量增加的对策
- 运转、维护的负担降低
- 资源使用率良好
为了达成这些目标,在系统设计时选择了一些在最初系统(NEX)设计和实现时不存在或者并不被广泛采纳的技术。
单体应用 vs 微服务
单体应用
- 服务之间的联动较为容易
- 发布周期(リリースサイクル,Release Cycle)会影响所有服务
- 很难看到各个服务各自的负载
微服务
- 服务之间的联动较为麻烦
- 各个服务的发布周期独自分离
- 各个服务的负载可独立观测
NPLN 的选择
- 基于强调各个服务开发和运营的独立性的考量,NPLN 使用微服务架构。
- 在实现上,任天堂采用了 Kubernetes 和 Istio 作为基础设施,并使用 Google Cloud 的 Google Kubernetes Engine(下称 GKE)和 Anthos 服务网格来构建系统。
单租户或多租户
租户(マルチテナント,Tenant):在本文中,一个游戏作品(タイトル,Title)相关的服务和基础设施统称为一个租户。
单租户
单租户即每个作品的服务实例、DB 均在单独的集群中隔离管理、维护。
优点:
- 易于定制
- 可扩展性的上限估计时仅需要考虑单个租户
- 易于隔离服务负载以及其他因素的影响
缺点:
- 随着租户数量的增加,管理难度将会上升
- 剩余的资源利用效率较低
译注:在演讲中,可扩展性的主要关注对象是 DB,所以下文中仅针对 DB 的可扩展性提供了解决方案。
多租户
多租户即所有 DB、游戏实例均在同一个集群中管理、维护。
优点:
- 即使租户数量增加,管理也会较为容易
- 剩余的资源使用效率较高
缺点:
- 定制难度高
- 可扩展性上限估计需要考虑多个租户
- 服务负载及其他因素影响可能会扩散到全体租户
NPLN 中的多租户
原则:基于多租户的设计,但是也允许部分单租户的混合配置系统。
对缺点的应对方案:
定制难度高
对于某些租户的特定逻辑,如对战匹配(マッチメイク,Matchmaking)等,使用 DSL 进行描述和抽象,使得开发者可以便捷定制开发。
可扩展性上限估计需要考虑多个租户
使用 Cloud Spanner 来作为主 DB 方案,这样在多个租户共享 DB 实例时也可以确保可扩展性。
服务负载及其他因素影响可能会扩散到全体租户
在必要的情况下通过对路由进行调整,可以针对某些租户的特定服务进行实例隔离,(译注:进而避免实例/集群等之间的影响)。
译注:这种情况其实称为混合租户(Hybrid-tenancy)更合理。
架构
架构图与大纲图
- 客户端,即 Nintendo Switch 侧,使用 C++ 开发 NPLN SDK
- 服务端,即 NPLN 服务器侧,使用 Go 语言进行开发,并将所有基础设施建设在 Google Cloud 中
- 客户端和服务端使用 gRPC 协议进行交互
- Nintendo Switch 主机之间使用 P2P 模式进行通信
- 客户端通过 Cloud Load Balancing 访问运行在 GKE 上的 NPLN 服务器
- 使用基于 Istio 的 Anthos 服务网格进行流量调度管理
- 使用基于 Agones 的 Game Servers 进行游戏服务器管理
- 日志等指标被发送到 BigQuery 进行分析
译注:
- 实际的通信方式是一种混合 P2P 模式,下文会进一步解释。
- 这里的 Game Servers 指的是 Google Cloud 的 Game Server 产品,并非我们传统意义上理解的独立游戏服务器(Dedicated Game Server),在下文中也会进一步解释。
微服务的实现方案
- 由于支持混合类型的多租户以及多种游戏服务,NPLN 的每个服务都运行在单独的 Pod 里面
- gRPC 的方法则将其构造成类似于 http 路径的方案,在 Istio 中基于路径进行路由
- 使用 VirtualService 将服务器和客户端之间的连接合二为一,而各个服务之间使用 namespace 来进行隔离。
多租户配置的实现
VirtualService 同样用于实现多租户配置。下面的三张图展示了不同主机运行不同游戏情况下的流量调度去向。
多集群配置的实现
- 在逻辑层面上,NPLN 有多个逻辑集群,如生产环境集群、测试环境集群以及管理平面集群等,目前使用多个 GKE 集群来进行分离
- 每个集群的配置都集中在管理平面集群上,通过管理后台(ダッシュボード,Dashboard)进行调整,并推送到各个集群的服务中
- 集群间的通信使用基于 mTLS 的 Istio 代理隧道进行
- 使用 Istio 的 VirtualService、DestinationRule、ServiceEntry、Gateway 等 CRD 组件来实现各个集群中服务间的透明访问
- 由于设计实现时 Anthos 的多集群功能在 beta 阶段,未来会考虑稳定后迁移到 Anthos 的多集群管理功能
使用 Google Cloud 时遇到的问题与解决方案
对战匹配系统流程
- Nintendo Switch 客户端提出对战匹配请求
- NPLN 服务器从 Cloud Spanner 中查找或者创建游戏 Session
- Agones 创建 Gamesync 实例
- Gamesync 将状态报告给 NPLN 服务器
- NPLN 服务器将 Gamesync 的信息返回给客户端
- 客户端各自连接 Gamesync
- 客户端之间开始 P2P 通信
Gamesync 是用于保存 Session 相关游戏状态的服务,一个 Session 对应一个 Gamesync 实例。举例来说,假定某一时刻有 100 万玩家在线,而这个游戏的在线模式每个 Session 有四位玩家,则我们可以估计这个时刻约有 25 万个不同的 Gamesync 实例。
Agones 与 Gamesync
Agones
- 用于管理游戏服务器的开源软件(OSS,Open Source Software)
- 游戏服务器群被称作 Fleet 进行管理,而每个游戏服务器使用 Pod 进行运行管理,在 NPLN 中即 Gamesync
- Controller 负责管理整个系统,Allocator 负责创建 GameServer 实例
- 游戏服务器状态管理
- Allocated:正在使用中
- Ready:可以使用
- FleetAutoscaler 可以对 Buffer 进行管理
- 监控并自动扩容,以满足服务稳定要求的最少 Ready 状态 Pod 数等
Gamesync
- Gamesync 是游戏 Session 管理服务器,用来对每个 Session 进行相关的状态管理
- 功能
- 强一致内存数据库,支持多个客户端对复杂数据结构的随机读写
- 游戏状态变更时进行实时通知
- 使用方式举例
- 管理在 P2P 中容易出现不一致的状态
- P2P 连接中的 Signaling 流程的处理
译注:根据数据挖掘的结果,任天堂在 NPLN SDK 中包含了 WebRTC 库,猜测在 NPLN 中使用了 WebRTC 来实现 P2P,而 Signaling 是 WebRTC 中一个专用服务器,起到了常规 NAT 穿越过程中 STUN/TURN 服务发现的功能。
所以,第二个使用方式中,可能的意思是 Gamesync 也会负责客户端之间 P2P NAT 穿越的工作。
Allocate 吞吐量过低的问题与解决
- Allocate 的职责
- Gamesync 发生状态变更时进行处理
- 将 Pod 的状态从 Ready 迁移到 Allocated
- Allocate 操作的吞吐量低于预期
- 创建了单独的 Forked Agones 来 Debug
- 猜测是 GKE 控制平面带来的延迟
- Cloud Trace 中没观察到显著延迟
- 专注于不可观测的控制平面部分
- 通过重复创建和删除轻量级 Pod 来进行实验,并最终确认性能没有到达预期,换句话说这就是问题所在
- 通过将预留 node 数量增加到 500 获得性能提升
- Pod 的操作 QPS 从 20 增加到 100
- Allocate 的吞吐量也确认得到提升
- 通过 Pod 重用进一步获得性能提升
- 默认在每个 Pod 使用完毕后直接删除
- 将删除操作修改为从 Allocated 状态迁移到 Ready
译注:在 GKE 文档中,明确提到了第一个解决方案相关的限制:
多集群化
- 单集群的性能上限
- Gamesync 分配速度受 GKE 控制平面性能限制
- 多集群提升可扩展性
- 由 NPLN 服务来分发请求到具体集群上
- 可基于集群数量来进行扩展
- 作为权衡,运营成本可能会增加
共享 Gamesync
- Google Cloud 的限制
- 每个区域的最大集群数量
- 大量的集群操作导致运营成本增加
- 资源消费效率低
- 解决方案:在一个 Gamesync 中共享多个 Session
- 从逻辑上分离 Gamesync
- 效果
- 集群操作量显著减少
- 改善资源使用效率
Game Servers 用例
- 用于管理多个 Agones 集群的 Google Cloud 托管服务
- NPLN 的用例
- 对多集群化的 Agones 进行资源管理
- 对 Fleet 进行金丝雀发布
- 对集群进行蓝绿发布
- 一种用例
- 在可以在 Game Servers 中创建不同的配置,并在发布时针对不同的大区(Realm)使用不同的配置。
多集群下 Agones 的资源管理
- 不使用 Game Servers 的情况下
- Operator 必须手动操作不同集群的 Agones 实例
- 使用 Game Servers 的情况下
- Game Servers 可以同步操作不同集群下的 Agones 实例,仅操作特定集群也是可以的
对 Fleet 进行金丝雀(Canary)发布
- 通过划分 Deployment 进行金丝雀发布
- 在确认正常工作后进行全量 Deployment
- 这样即使新版本不符合预期也不会影响整体服务
- 出现问题时,非线上版本的 Pod 只有极小部分
- 如果出现问题,则逐个回滚
对集群进行蓝绿(Blue/Green)发布
- 在 GKE 和 Agones 更新时使用
- 出现问题则立即回滚
- Game Servers
- 增加新集群,删除旧集群
- NPLN 服务
- 增加新集群的端点
- 删除旧集群的端点
- 旧集群清理
- 当 Allocated 状态的 Gamesync 到达 0 后则删除旧集群
GKE 节点池更新时操作
- 保证节点上已经没有用户了再进行更新
- 在此节点上不能创建新的 Session
- 处于 Allocated 状态的 Pod 不能删除
- 对 Gamesync 使用 Kubernetes 的 Cordon 标记功能
- Cordon 状态的 node 上不会被创建新的 Pod
- 处于 Allocated 状态的 Pod 不会被删除
- 可以保证在用户无感知的情况下进行更新
Spanner 的用例
- 原则上将 Spanner 用于需要直接面向用户的服务
- 在对战匹配中
- 管理 Session 的状态
- 读写操作发生频率较高
- 按实例与服务为单元进行隔离
已删除行的问题
- Spanner 中已删除的行会物理上保留一段时间
- 在几天到一周的时间内会被实际删除掉
- 这种等待被删除的行被称作 Tombstone
- 在长期测试中,搜索性能会逐渐下降
- 当某一行被删除后,由于这一行实际还会存在一段时间,在此之后才不会被扫描到
- 基于 Tombstone 存在状态下的考虑
- 基于性能损失的前提下,实验出需要的节点数量
- 考察高峰期将持续多长时间
译注:根据官方文档的说法,这个可能是因为 Spanner TTL 的配置出现了问题。
CI 自动测试时的问题
- Spanner 模拟器的功能缺失
- 功能缺失带来的问题
- 无法并行执行多个事务 → 无法确定是否存在多事务竞争
- 无法使用特定的 HINT 语句 → 无法确定是否带来性能优化
- 无法获取统计数据 → 无法确定是否特定修改劣化了性能
- 根据测试分支的重要性,跳过一部分测试
- 功能缺失带来的问题
- 使用真实 Spanner 实例进行自动化测试的问题
- 在自动化测试环境中使用 Cloud Build 并尽可能并行化
- 高频创建和删除数据库时会带来的延迟
- 增加测试并行度时最大 DB 数的限制
Summary
- NPLN 是基于微服务云原生的现代架构,作为任天堂的通用在线游戏基础设施为未来新游戏提供服务,强调了多团队合作下的扩展性和维护性,提升研发效能,降低运营成本。
- 在架构设计上,整套系统使用了基于 Service Mesh 的微服务方案,并通过 Kubernetes+Agones 来管控整个系统的资源,客户端则使用 gRPC 进行通信。
- 整套架构构建在 Google Cloud 上,并在基础设施上使用了特定产品:GKE、Spanner,BigQuery 等。
- 整套架构支持现代开发和观测流程,包括 CI/CD 自动化测试,Canary、Blue/Green 发布,基于大型数据仓库的近实时/离线数据分析等。