目录

任天堂 NPLN 服务架构演讲学习笔记

NPLN 是任天堂的新一代的通用多人在线游戏网络系统,旨在替代自 3DS 时代就使用的 NEX 系统,采用全面的现代架构来进行开发,目标是在玩家无感知的情况下替换掉 NEX。NPLN 自 2018 年开始开发,全面基于 Google Cloud,并于 2021 年首先在《怪物猎人:崛起》中进行使用。

截至目前,接入 NPLN 的游戏有下面三款:

  • 怪物猎人:崛起
  • 宝可梦传说:阿尔宙斯
  • Splatoon 3

这篇文章大体上是任天堂于 2022 年 4 月,在 Google 日本主办的 Google Cloud Day: Digital ’22 活动上做的主题演讲的笔记,内容大部分来自官方 slider 的翻译和截图。这次分享也是现在网上能找到最全面的 NPLN 服务端资料(SDK 暂未全面开放给所有开发者,任天堂自己采用面也不广),故特此总结以飨读者。由于我个人日语是个二把刀,内容如果有翻译不准确的地方,还请不吝赐教。

演讲材料:视频Slider

阅读提示

注意,这篇文章无法回答以下问题:

  1. 为什么 Splatoon 3 联机还是这么烂?

    NPLN 最大的更新是在游戏的其他在线体验上,而对局的联机方案还是跟 Splatoon 2 一样的基于 P2P 的方案。也就是说,2 代存在的问题,3 代大概仍然存在。

    有种说法是 NPLN 有 UDP Relay,在 NAT 打洞失败后会 Fallback 到这种模式,由于官方演讲没有提及,我在此无法下定论。(根据数据挖掘的结果,NPLN 也许使用了基于 WebRTC 的 TURN 模式)

  2. 具体 XXX 技术是怎么实现的?

    目前由于 SDK 没有开放,同时逆向分析难度较大,本文仅总结任天堂在 GCD ‘22 上公开的部分,特别细节的部分暂时不知道。

  3. 这篇文章会涉及到具体某个游戏吗?

    不会。本文只包含了 NPLN 服务端的整体架构和实现分析。对于游戏怎么接入的、SDK 怎么使用的等等都不包含在内。

设计原则

NPLN 的架构是微服务化的、面向多团队的合作开发架构。

为什么使用微服务?

  • 以微服务为导向的设计:游戏开发规模扩大的对策
    • 需要支持的游戏服务和能力在增加
    • 任天堂内部使用这套系统的开发者也在增加
  • 支持多租户的设计:需要使用这套系统的作品数量增加的对策
    • 运转、维护的负担降低
    • 资源使用率良好

为了达成这些目标,在系统设计时选择了一些在最初系统(NEX)设计和实现时不存在或者并不被广泛采纳的技术。

单体应用 vs 微服务

单体应用

https://assets.lxdlam.com/img/1665341584_f89806c7.png
  • 服务之间的联动较为容易
  • 发布周期(リリースサイクル,Release Cycle)会影响所有服务
  • 很难看到各个服务各自的负载

微服务

https://assets.lxdlam.com/img/1665341564_e02b3876.png
  • 服务之间的联动较为麻烦
  • 各个服务的发布周期独自分离
  • 各个服务的负载可独立观测

NPLN 的选择

  • 基于强调各个服务开发和运营的独立性的考量,NPLN 使用微服务架构。
  • 在实现上,任天堂采用了 Kubernetes 和 Istio 作为基础设施,并使用 Google Cloud 的 Google Kubernetes Engine(下称 GKE)和 Anthos 服务网格来构建系统。

单租户或多租户

租户(マルチテナント,Tenant):在本文中,一个游戏作品(タイトル,Title)相关的服务和基础设施统称为一个租户。

单租户

https://assets.lxdlam.com/img/1665341570_489c5535.png

单租户即每个作品的服务实例、DB 均在单独的集群中隔离管理、维护。

优点:

  • 易于定制
  • 可扩展性的上限估计时仅需要考虑单个租户
  • 易于隔离服务负载以及其他因素的影响

缺点:

  • 随着租户数量的增加,管理难度将会上升
  • 剩余的资源利用效率较低

译注:在演讲中,可扩展性的主要关注对象是 DB,所以下文中仅针对 DB 的可扩展性提供了解决方案。

多租户

https://assets.lxdlam.com/img/1665341565_00aa09a4.png

多租户即所有 DB、游戏实例均在同一个集群中管理、维护。

优点:

  • 即使租户数量增加,管理也会较为容易
  • 剩余的资源使用效率较高

缺点:

  • 定制难度高
  • 可扩展性上限估计需要考虑多个租户
  • 服务负载及其他因素影响可能会扩散到全体租户

NPLN 中的多租户

https://assets.lxdlam.com/img/1665341580_08f754e4.png

原则:基于多租户的设计,但是也允许部分单租户的混合配置系统。

对缺点的应对方案:

  • 定制难度高

    对于某些租户的特定逻辑,如对战匹配(マッチメイク,Matchmaking)等,使用 DSL 进行描述和抽象,使得开发者可以便捷定制开发。

  • 可扩展性上限估计需要考虑多个租户

    使用 Cloud Spanner 来作为主 DB 方案,这样在多个租户共享 DB 实例时也可以确保可扩展性。

  • 服务负载及其他因素影响可能会扩散到全体租户

    在必要的情况下通过对路由进行调整,可以针对某些租户的特定服务进行实例隔离,(译注:进而避免实例/集群等之间的影响)。

译注:这种情况其实称为混合租户(Hybrid-tenancy)更合理。

架构

架构图与大纲图

https://assets.lxdlam.com/img/1665341577_9d53ab65.png
  • 客户端,即 Nintendo Switch 侧,使用 C++ 开发 NPLN SDK
  • 服务端,即 NPLN 服务器侧,使用 Go 语言进行开发,并将所有基础设施建设在 Google Cloud 中
  • 客户端和服务端使用 gRPC 协议进行交互
https://assets.lxdlam.com/img/1665341571_2b32a4f8.png
  • Nintendo Switch 主机之间使用 P2P 模式进行通信
  • 客户端通过 Cloud Load Balancing 访问运行在 GKE 上的 NPLN 服务器
  • 使用基于 Istio 的 Anthos 服务网格进行流量调度管理
  • 使用基于 Agones 的 Game Servers 进行游戏服务器管理
  • 日志等指标被发送到 BigQuery 进行分析

译注:

  1. 实际的通信方式是一种混合 P2P 模式,下文会进一步解释。
  2. 这里的 Game Servers 指的是 Google Cloud 的 Game Server 产品,并非我们传统意义上理解的独立游戏服务器(Dedicated Game Server),在下文中也会进一步解释。

微服务的实现方案

https://assets.lxdlam.com/img/1665344199_4074c169.png
  • 由于支持混合类型的多租户以及多种游戏服务,NPLN 的每个服务都运行在单独的 Pod 里面
  • gRPC 的方法则将其构造成类似于 http 路径的方案,在 Istio 中基于路径进行路由
  • 使用 VirtualService 将服务器和客户端之间的连接合二为一,而各个服务之间使用 namespace 来进行隔离。

多租户配置的实现

VirtualService 同样用于实现多租户配置。下面的三张图展示了不同主机运行不同游戏情况下的流量调度去向。

https://assets.lxdlam.com/img/1665341586_ed526cbc.png https://assets.lxdlam.com/img/1665341581_d38f25d7.png https://assets.lxdlam.com/img/1665341574_d6864e99.png

多集群配置的实现

https://assets.lxdlam.com/img/1665341572_b8f1c35d.png
  • 在逻辑层面上,NPLN 有多个逻辑集群,如生产环境集群、测试环境集群以及管理平面集群等,目前使用多个 GKE 集群来进行分离
  • 每个集群的配置都集中在管理平面集群上,通过管理后台(ダッシュボード,Dashboard)进行调整,并推送到各个集群的服务中
  • 集群间的通信使用基于 mTLS 的 Istio 代理隧道进行
  • 使用 Istio 的 VirtualService、DestinationRule、ServiceEntry、Gateway 等 CRD 组件来实现各个集群中服务间的透明访问
  • 由于设计实现时 Anthos 的多集群功能在 beta 阶段,未来会考虑稳定后迁移到 Anthos 的多集群管理功能

使用 Google Cloud 时遇到的问题与解决方案

对战匹配系统流程

https://assets.lxdlam.com/img/1665341578_db029fdb.png
  1. Nintendo Switch 客户端提出对战匹配请求
  2. NPLN 服务器从 Cloud Spanner 中查找或者创建游戏 Session
  3. Agones 创建 Gamesync 实例
  4. Gamesync 将状态报告给 NPLN 服务器
  5. NPLN 服务器将 Gamesync 的信息返回给客户端
  6. 客户端各自连接 Gamesync
  7. 客户端之间开始 P2P 通信

Gamesync 是用于保存 Session 相关游戏状态的服务,一个 Session 对应一个 Gamesync 实例。举例来说,假定某一时刻有 100 万玩家在线,而这个游戏的在线模式每个 Session 有四位玩家,则我们可以估计这个时刻约有 25 万个不同的 Gamesync 实例。

Agones 与 Gamesync

Agones

https://assets.lxdlam.com/img/1665341579_c0ed5354.png
  • 用于管理游戏服务器的开源软件(OSS,Open Source Software)
  • 游戏服务器群被称作 Fleet 进行管理,而每个游戏服务器使用 Pod 进行运行管理,在 NPLN 中即 Gamesync
  • Controller 负责管理整个系统,Allocator 负责创建 GameServer 实例
  • 游戏服务器状态管理
    • Allocated:正在使用中
    • Ready:可以使用
  • FleetAutoscaler 可以对 Buffer 进行管理
    • 监控并自动扩容,以满足服务稳定要求的最少 Ready 状态 Pod 数等

Gamesync

https://assets.lxdlam.com/img/1665341567_aa46629d.png
  • Gamesync 是游戏 Session 管理服务器,用来对每个 Session 进行相关的状态管理
  • 功能
    • 强一致内存数据库,支持多个客户端对复杂数据结构的随机读写
    • 游戏状态变更时进行实时通知
  • 使用方式举例
    • 管理在 P2P 中容易出现不一致的状态
    • P2P 连接中的 Signaling 流程的处理

译注:根据数据挖掘的结果,任天堂在 NPLN SDK 中包含了 WebRTC 库,猜测在 NPLN 中使用了 WebRTC 来实现 P2P,而 Signaling 是 WebRTC 中一个专用服务器,起到了常规 NAT 穿越过程中 STUN/TURN 服务发现的功能。

所以,第二个使用方式中,可能的意思是 Gamesync 也会负责客户端之间 P2P NAT 穿越的工作。

Allocate 吞吐量过低的问题与解决

https://assets.lxdlam.com/img/1665341568_aef5d68f.png
  • Allocate 的职责
    • Gamesync 发生状态变更时进行处理
    • 将 Pod 的状态从 Ready 迁移到 Allocated
  • Allocate 操作的吞吐量低于预期
    • 创建了单独的 Forked Agones 来 Debug
  • 猜测是 GKE 控制平面带来的延迟
    • Cloud Trace 中没观察到显著延迟
    • 专注于不可观测的控制平面部分
https://assets.lxdlam.com/img/1665341563_dc686dd5.png
  • 通过重复创建和删除轻量级 Pod 来进行实验,并最终确认性能没有到达预期,换句话说这就是问题所在
  • 通过将预留 node 数量增加到 500 获得性能提升
    • Pod 的操作 QPS 从 20 增加到 100
    • Allocate 的吞吐量也确认得到提升
  • 通过 Pod 重用进一步获得性能提升
    • 默认在每个 Pod 使用完毕后直接删除
    • 将删除操作修改为从 Allocated 状态迁移到 Ready

译注:在 GKE 文档中,明确提到了第一个解决方案相关的限制:

https://assets.lxdlam.com/img/1665341561_0fcfa6f4.png

多集群化

https://assets.lxdlam.com/img/1665341591_a0b1d942.png
  • 单集群的性能上限
    • Gamesync 分配速度受 GKE 控制平面性能限制
  • 多集群提升可扩展性
    • 由 NPLN 服务来分发请求到具体集群上
  • 可基于集群数量来进行扩展
    • 作为权衡,运营成本可能会增加

共享 Gamesync

https://assets.lxdlam.com/img/1665341595_c740d230.png
  • Google Cloud 的限制
    • 每个区域的最大集群数量
    • 大量的集群操作导致运营成本增加
    • 资源消费效率低
  • 解决方案:在一个 Gamesync 中共享多个 Session
    • 从逻辑上分离 Gamesync
  • 效果
    • 集群操作量显著减少
    • 改善资源使用效率

Game Servers 用例

https://assets.lxdlam.com/img/1665341588_01f5e6ad.png
  • 用于管理多个 Agones 集群的 Google Cloud 托管服务
  • NPLN 的用例
    • 对多集群化的 Agones 进行资源管理
    • 对 Fleet 进行金丝雀发布
    • 对集群进行蓝绿发布
  • 一种用例
    • 在可以在 Game Servers 中创建不同的配置,并在发布时针对不同的大区(Realm)使用不同的配置。

多集群下 Agones 的资源管理

https://assets.lxdlam.com/img/1665341587_3757a235.png
  • 不使用 Game Servers 的情况下
    • Operator 必须手动操作不同集群的 Agones 实例
  • 使用 Game Servers 的情况下
    • Game Servers 可以同步操作不同集群下的 Agones 实例,仅操作特定集群也是可以的

对 Fleet 进行金丝雀(Canary)发布

https://assets.lxdlam.com/img/1665341583_b45e0db7.png
  • 通过划分 Deployment 进行金丝雀发布
    • 在确认正常工作后进行全量 Deployment
  • 这样即使新版本不符合预期也不会影响整体服务
    • 出现问题时,非线上版本的 Pod 只有极小部分
    • 如果出现问题,则逐个回滚

对集群进行蓝绿(Blue/Green)发布

https://assets.lxdlam.com/img/1665341585_ade02d9a.png
  • 在 GKE 和 Agones 更新时使用
    • 出现问题则立即回滚
  • Game Servers
    • 增加新集群,删除旧集群
  • NPLN 服务
    • 增加新集群的端点
    • 删除旧集群的端点
  • 旧集群清理
    • 当 Allocated 状态的 Gamesync 到达 0 后则删除旧集群

GKE 节点池更新时操作

https://assets.lxdlam.com/img/1665341592_cbd8a0af.png
  • 保证节点上已经没有用户了再进行更新
    • 在此节点上不能创建新的 Session
    • 处于 Allocated 状态的 Pod 不能删除
  • 对 Gamesync 使用 Kubernetes 的 Cordon 标记功能
    • Cordon 状态的 node 上不会被创建新的 Pod
    • 处于 Allocated 状态的 Pod 不会被删除
    • 可以保证在用户无感知的情况下进行更新

Spanner 的用例

https://assets.lxdlam.com/img/1665341593_3b2ec14a.png
  • 原则上将 Spanner 用于需要直接面向用户的服务
  • 在对战匹配中
    • 管理 Session 的状态
    • 读写操作发生频率较高
  • 按实例与服务为单元进行隔离

已删除行的问题

https://assets.lxdlam.com/img/1665341597_83373ee1.png
  • Spanner 中已删除的行会物理上保留一段时间
    • 在几天到一周的时间内会被实际删除掉
    • 这种等待被删除的行被称作 Tombstone
  • 在长期测试中,搜索性能会逐渐下降
    • 当某一行被删除后,由于这一行实际还会存在一段时间,在此之后才不会被扫描到
  • 基于 Tombstone 存在状态下的考虑
    • 基于性能损失的前提下,实验出需要的节点数量
    • 考察高峰期将持续多长时间

译注:根据官方文档的说法,这个可能是因为 Spanner TTL 的配置出现了问题。

CI 自动测试时的问题

https://assets.lxdlam.com/img/1665341596_e6f17ede.png
  • Spanner 模拟器的功能缺失
    • 功能缺失带来的问题
      • 无法并行执行多个事务 → 无法确定是否存在多事务竞争
      • 无法使用特定的 HINT 语句 → 无法确定是否带来性能优化
      • 无法获取统计数据 → 无法确定是否特定修改劣化了性能
    • 根据测试分支的重要性,跳过一部分测试
  • 使用真实 Spanner 实例进行自动化测试的问题
    • 在自动化测试环境中使用 Cloud Build 并尽可能并行化
    • 高频创建和删除数据库时会带来的延迟
    • 增加测试并行度时最大 DB 数的限制

Summary

  • NPLN 是基于微服务云原生的现代架构,作为任天堂的通用在线游戏基础设施为未来新游戏提供服务,强调了多团队合作下的扩展性和维护性,提升研发效能,降低运营成本。
  • 在架构设计上,整套系统使用了基于 Service Mesh 的微服务方案,并通过 Kubernetes+Agones 来管控整个系统的资源,客户端则使用 gRPC 进行通信。
  • 整套架构构建在 Google Cloud 上,并在基础设施上使用了特定产品:GKE、Spanner,BigQuery 等。
  • 整套架构支持现代开发和观测流程,包括 CI/CD 自动化测试,Canary、Blue/Green 发布,基于大型数据仓库的近实时/离线数据分析等。