使用 Cloud SQL for MySQL Second Generation 作为移动游戏后端数据库

Last reviewed 2022-10-28 UTC

这个经过良好测试,用于构建在线游戏后端的模式使用关系型数据库,例如 MySQL。该数据库存储游戏世界状态和基本的永久性数据。对于基本的基于会话的游戏,最终比赛结果是数据库存储的最复杂的数据。 对于大型、永久世界 (persistent-world) 和大型多人在线 (MMO) 游戏,数据库可能要存储一组相互关联的表格,用于记录玩家进度和库存。后端数据库层中的查询速度会直接影响用户在游戏客户端上的响应体验。

虽然这种模式很常见,但大多数游戏开发团队都没有专门的数据库管理员,随着数据库规模的扩大和模型关系复杂性的增加,许多团队都会想要将这些管理任务委托给专人打理。对于中小型、异步、回合制移动游戏,Google Cloud SQL 之类的数据库是很好的选择。其中,Cloud SQL for MySQL Second Generation 提供完全托管和全面管理的 MySQL 实例,具有可靠的性能、最少的操作和自动备份功能。

面向服务的数据库模式设计

数据库架构概览。

微服务范例对移动游戏数据库后端是很有用的。 一种常见的设计是使数据库面向数据库服务,该数据库服务由一个工作器进程池组成,它接受来自游戏前端的查询请求,对数据库运行这些查询,并返回结果。

面向服务的数据库模式的优点

让中间服务代表您的游戏服务器进行数据库查询有以下几个优点:

  • 提高可用性 - 数据库通常会限制并发连接的数量。通过使用服务,能够向数据库发出请求的游戏服务器的数量不再受到最大允许连接数限制。
  • 容错 - 可以构建能够在数据库遇到问题时临时处理请求的数据库服务。
  • 优化请求 - 数据库服务可以优化数据库请求,提供以下功能:
    • 查询验证。
    • 排定查询优先级。
    • 查询率流量控制。
    • 内存中的直读缓存。
  • 数据库抽象 - 只要满足服务合约,就可以替换数据库服务及其支持数据库,而无需修改前端游戏服务器。这种设计使得在开发或质量检查环境中使用不同的数据库,或者在生产中迁移到不同的数据库技术成为可能。

面向服务的游戏数据库模式

下图说明了如何使用 Google Cloud Platform 服务构建强大的、面向服务的数据库模式。

使用 Google Cloud Platform 的数据库模式。

可以使用以下组件构建强大的、面向服务的数据库模式:

  • 专用游戏服务器/前端 - 游戏客户端应用直接连接到游戏服务器,使其成为前端服务。 专用游戏服务器通常是使用游戏引擎构建的自定义可执行文件,需要在诸如 Google Compute Engine 虚拟机之类的虚拟化硬件上运行。如果您正在编写一个可以使用 HTTP 样式的请求/响应语义建模在线交互的游戏,那么 Google App Engine 也不失为一个好方案。

  • 游戏服务器与数据库服务的通信 - 通常使用创建、读取、更新和删除 (CRUD) 的访问方式进行建模,因此可以使用 Compute Engine 上的 REST API 或 gRPC 端点。

  • API/RPC 端点与数据库工作器池的通信 - 常见的排队场景,热门方案包括自行管理的、开源的、排队中间件,如 RabbitMQZeroMQ。在 Cloud Platform 上,您可以使用安全、耐用且可用性高的 Google Cloud Pub/Sub。它提供了托管排队解决方案,无需管理服务器。

  • 连接到 Cloud SQL Second Generation 的数据库工作器 - 数据库工作器可以使用提供最新 MySQL 访问方法的任何语言编写。这些工作器可以在 Compute Engine 上手动管理,也可以打包在 Docker 容器中,以便使用 Google Kubernetes Engine 上的 Kubernetes DSL 轻松管理。

使用 Cloud SQL Second Generation 时需要考虑的一些限制:

  • 数据库大小限制为 10 TB。
  • 连接限制为 4000 个并发连接。
  • 虽然支持副本,但不支持 NDB(分片)。

要解决这些问题,请使用以下方法:

  • 选择优化存储数据量的数据模型。

  • 合并那些用于将很少访问的数据移出主数据库并存入数据仓库的流程,例如 Google BigQuery。

  • 使用游戏服务器通过微服务访问数据库的模式。 即使玩家数量达到您的游戏服务器可以直接访问数据库的程度,这种将数据库层与游戏服务器分离的模式有许多优点,例如排队、查询速率调整、连接失败容限等。此外,在游戏变得流行之后尝试添加单独的数据库访问层会导致停机时间和收入损失。

  • 针对数据库的只读副本进行游戏分析和玩家遥测。这可以防止您的分析影响数据库的响应能力。Cloud SQL for MySQL Second Generation 允许标准的 MySQL 复制,并且您可以针对分析查询适当调整第二个实例的大小,以使成本保持在预算内。

样本设计:大型单人社交 (MASS) 游戏

在过去十年中,一种新兴的游戏范式是大量单人玩家同时在线玩游戏,其社交机制包括单元借阅、单元交易和排行榜,这些是玩家之间唯一的接触点。Puzzle and Dragons™ 和 Monster Strike™ 就是大型单人社交移动游戏的佼佼者。通过客户端/服务器通信,大型单人社交移动游戏将变得经济实惠。这使得用户即使在有限或零星连接的情况下也可以畅玩游戏。此类游戏的几乎所有永久性状态存储都与元游戏(收集单元和维护玩家货币)相关,并产生两种基本类型的对象以存储在数据库中。使用 CRUD 机制即可轻松操作这些对象。

玩家对象,用于跟踪:

  • 游戏和真实货币。
  • 单元库存栏位的总数。
  • 体力。
  • 经验。

单元对象,用于跟踪:

  • 所有者(玩家 ID)。
  • 经验。
  • 获得成本。
  • 单元库存。

如果游戏的玩家总数不超过 100000 名,则此数据模型非常适合用于关系型数据库,例如 Cloud SQL Second Generation。

Mimus

Mimus 是一款模拟大型单人社交移动游戏应用,具有 Puzzle and Dragons™ 或 Monster Strike™ 风格的后端。它假设每个玩家一次只能从一个设备登录,并且必须一个接一个地完成操作。使用 Mimus,您就可以运行模拟工作负载来评估所需架构的最佳容量,而该容量通常取决于并发用户数 (CCU)。

您可以在 https://github.com/GoogleCloudPlatform/mimus-game-simulator 获得 Mimus 源代码。

Mimus 架构概览

Mimus 服务器内的 Mimus 客户端模拟

在大型单人社交游戏中,游戏开发者可以通过要求玩家观看动画并与游戏客户端交互以继续游戏的方式来控制游戏客户端生成数据库查询的速率。考虑到 Mimus 使用 sleep() 调用模拟这些速率限制策略,那么即可通过运行与模拟玩家一样多的进程来模拟每个客户端数据库负载的合理近似值。使用 Kubernetes 集群(针对数据库生成查询)中的容器化 Mimus 客户端/服务器 pod,即可对此操作进行有效编排。

Mimus 游戏客户端使用连续循环模拟与后端服务器的通信,该循环直接调用 Mimus 服务器过程,根据玩家的状态及其库存选择函数调用。

Mimus 模拟的玩家行为包括:

  • 打一场比赛。
  • 购买货币。
  • 消费货币。
  • 升级或进化单元。

上述每一个操作都实现为与玩家对象或单元对象的多个 CRUD 交互,由客户端通过调用 Mimus 服务器来操纵。Mimus 服务器使用同步(阻塞)Mimus 数据库 API 发出这些数据库请求。此 API 是 Mimus 服务器导入的 Python 模块,可以配置为测试不同的数据库后端实现。

Mimus 数据库 API 与 Mimus 数据库工作器池的通信

下图说明了 Mimus 服务器与数据库服务之间的通信。

Mimus 的通信设计。

Mimus 数据库 API 接受批量数据库查询,并返回结果。它将这些批量数据库查询发布为 Cloud Pub/Sub 消息,并等待通过 Redis 返回结果。在发送消息之前,数据库 API 会验证将写入数据库的所有值,并使用唯一的事务 ID 标记消息。然后,该消息将发布到 Cloud Pub/Sub 中的 Work 主题。接下来,数据库 API 循环,轮询是否用作 Redis 密钥的事务 ID 存在数据库内。然后,数据库 API 检索密钥值的结果,并将其返回到 Mimus 服务器,之后提供给 Mimus 客户端。

通信选择的考虑因素

Mimus 需要使用 Cloud Pub/Sub 以进行挂起查询的通信,这是因为用户操作需要耐用性和可靠的交付。如果 Mimus 使用 Redis 来传递结果,那么耐用性和可靠性就不那么重要了。 即使应用错误或 Redis 故障导致结果丢失,Mimus 客户端也可以再次查询以从数据库获取最终结果。通过在关系型数据库中使用事务,可以保证发生所有请求一起更改,或者不进行任何更改。对于 Mimus 需要发出第二个请求的罕见情况,一般认为这是可接受的权衡,因为这可以换取 Redis 实现的便捷检索。要将 Redis 的使用控制在合理水平,其发出的请求结果在 30 秒后就失效。

Mimus 数据库工作器池

Mimus 数据库工作器池包含在 Kubernetes Engine 上运行的多个进程。每个正在运行的容器实例在无限循环中轮询 Work Cloud Pub/Sub 主题以获取新消息。当收到消息时,它使用 Python MySQLdb 模块在消息中运行查询。单个消息表示关系型事务,可能包含多个查询。在提交到数据库之前,必须完成消息中的所有查询。在查询完成(或失败)之后,数据库工作器将结果作为原始消息的一部分按照接收的事务 ID 发布到 Redis。

Mimus 数据库

支持 Mimus 数据库服务的关系型数据库是一种 Cloud SQL for MySQL Second Generation 实例。创建此实例时,您可以选择最多 32 颗核心和 208 GB RAM 的机器配置,最大磁盘大小为 10 TB。除了支持副本之外,Cloud SQL Second Generation 实例还可以默认配置为常规备份。如果需要额外的 MySQL 调节,可以按照 Cloud SQL 实例上的 MySQL 标志设置特定的 MySQL 标志。如需了解更多配置信息,请参阅 Cloud SQL 文档

使用 Mimus 测试 Cloud SQL 的结论

Cloud SQL SG 机器类型 建议的并发用户数
n1-standard-4 15000
n1-standard-8 30000
n1-standard-16 60000
n1-standard-32 120000
n1-highmem-8 50000
n1-highmem-16 100000
n1-highmem-32 200000

当使用 Mimus 自动化测试框架来模拟在 n1-highmem-16 Compute Engine 实例上创建的 Cloud SQL for MySQL Second Generation 实例的 100000 个并发用户时,在整个测试期间,查询响应时间保持在 2 秒以内。

如果您正在构建大型单人社交移动游戏并需要支持数十万个并发玩家,那么基于 Cloud SQL for MySQL Second Generation 的面向服务的数据库模式可以提供您所需的性能。 如果游戏越来越受欢迎,您可以在数据库服务级别引入其他 Cloud SQL 实例(分片或副本)以及 Redis 或 Memcached 缓存策略,以将性能保持在可接受的水平。

对于预计并发用户较少的游戏,您可以使用小型的机器类型来降低成本。

对于预计有数百万并发用户的游戏,您应该考虑使用具有强大的可扩缩性和性能特征的 NoSQL 数据库,例如 Google Cloud Datastore 或 Google Cloud Bigtable。

后续步骤