使用 Azure 容器实例 (ACI) 实现同步多人游戏

探索此使用 Azure 容器实例、事件网格和 Azure Functions 的替代解决方案,它可按需自动缩放,且按每秒使用量计费,前提是您希望以支付较高价格为代价,利用托管虚拟机实现更简单的体系结构和维护工作。

服务器需要在 Azure 容器实例之外保持持久状态。

本文将描述 GitHub 上的此示例中使用的体系结构。 请注意,此参考体系结构中的代码只是一个指南示例,在用于生产环境之前,可能还有需要优化的地方。

体系结构关系图

S使用 Azure 容器实例实现同步多人游戏

相关服务

  • Azure 流量管理器 - 选择此服务是因为它可以根据延迟情况将玩家连接到最合适的区域。
  • Azure 容器实例 - 在 Azure 中运行容器的最快且最简单的方法,无需管理任何虚拟机,也无需采用更高级别的服务。
  • Azure Functions - 选择此服务是因为它是运行小部分逻辑的最简单方法。
  • Azure 表存储 - 用于跟踪容器组状态的简单的键/属性存储。
  • Azure 事件网格 - 选择此服务是因为它内置针对来自 Azure 服务的事件的支持。
  • Azure Blob 存储 - 我们需要一个用于存储自动缩放帮助程序配置的空间。
  • 资源组 - 针对 Azure 流量管理器使用一个资源组,并针对每个区域游戏服务器池各使用一个资源组(例如,一个用于北美、一个用于欧洲、一个用于拉丁美洲、一个用于亚洲等)。

部署模板

单击下面的按钮,将项目部署到您的 Azure 订阅:

Deploy using an Azure Resource Manager template

此操作将触发模板部署,即系统会将 azuredeploy.json ARM 模板文件部署到您的 Azure 订阅,从而创建必要的 Azure 资源并从此存储库中提取源代码。 这可能会在您的 Azure 帐户中产生相应费用。

请查看一般指南文档,其中有一篇文章概述了 Azure 服务的命名规则和限制。

要部署项目,需要指定以下信息:

  • 位置:选择将部署资源的 Azure 区域。 请确保选择 Azure 容器实例可用的位置
  • Function 名称:选择 Function App 的唯一名称。 这将确定 Function 的 DNS,请谨慎选择。
  • 存储库 URL:系统将拉取相应文件来创建 Azure Functions,而此 URL 将确定包含这些文件的存储库。 您可以保留默认值,或者换成自己的 Fork。
  • 分支:此值与项目的 GitHub 分支相对应。

Azure Functions 部署在免费的应用程序服务计划上,您可能需要对它进行扩展以提高性能。

项目使用托管服务标识及其与 Azure Functions 的关系来对 Azure ARM API 管理服务进行身份验证,从而创建/删除/修改所需的 Azure 容器实例。 部署脚本会自动为 Function App 创建应用程序标识,但您需要向将要托管容器实例的资源组授予此标识权限。 为此,请执行以下操作:

  • 访问 Azure 门户
  • 找到要在其中创建 Azure 容器实例的资源组(这可能是托管 Function App 的同一资源组)。
  • 选择访问控制 (IAM)
  • 单击添加,选择贡献者作为角色,向 Function App 分配访问权限,然后通过修改订阅/资源组下拉框来选择 Azure Functions。
  • 单击保存,就完成操作了!

此外,部署完成后,您需要按照此处的说明手动添加 ACIMonitor Function 的事件订阅 Webhook。 只需确保选择正确的资源组来监控事件(即将在其中创建容器的 Azure 资源组)。 这样一来,只要指定资源组中发生资源修改,事件网格便会立即向 ACIMonitor Function 发送消息。 完成此操作后,部署就准备就绪了。 获取 ACIMonitor Function 的 URL 后,您可以使用 ARM 模板来部署事件网格订阅,这是一项可选操作。

使用门户部署事件网格订阅时,需要填写以下值:

  • 名称 - 为事件网格订阅选择一个独特的名称。
  • 主题类型 - 选择“资源组”(如果 Azure 容器实例将部署在不同的资源组中,请选择“Azure 订阅”)。
  • 订阅 - 要用来监控 Azure 容器实例创建事件的订阅。
  • 资源组 - 选择将包含您创建的 Azure 容器实例的资源组。 请确保选中订阅所有事件类型复选框。
  • 订阅服务器类型 - Webhook。
  • 订阅终结点 - 这将包含 ACIMonitor Azure Functions 的触发器 URL。
  • 前缀筛选器 - 将其保留为空。
  • 后缀筛选器 - 将其保留为空。

最后但同样重要的是,使用新的 Azure Functions v2 运行时版本时,EventGrid 绑定扩展可能需要手动注册。 在正常情况下,扩展将自动安装(因为它注册在 extensions.csproj 文件中),但如果没有自动安装,您可以查看以下文章了解如何手动执行此操作:

分步操作

  1. 玩家的设备客户端连接到流量管理器,以传送要查找游戏服务器的玩家请求。
  2. 流量管理器连接到具有最低延迟的区域,并指向可获取可用游戏服务器的 Matchmaker。
  3. Matchmaker 调用 ACIList Azure Functions 以获取来自所有容器组的公共 IP 和活动会话列表。
  4. 该 Azure Functions 从 Azure 表存储中获取表,而该表存储将存储所有容器组中的公共 IP、活动会话数量和状态。
  5. 假设没有可用的表,系统将调用 ACICreate Azure Functions。 容器状态为 Creating
  6. ACICreate Azure Functions 创建容器。
  7. 创建或删除容器时,系统接入事件网格进行侦听。 片刻之后(几分钟),事件网格收到来自正在创建的容器的相关事件。
  8. 事件网格还进行了设置,可在收到事件后调用 ACIMonitor Azure Functions,以便传递公共 IP。
  9. ACIMonitor Azure Functions 插入来自新创建容器的公共 IP,并将该容器的状态设置为 Running
  10. Matchmaker 现在为玩家获得了一台可用服务器,它将详细信息发回设备客户端,以便设备客户端直接连接到服务器。
  11. Matchmaker 调用 ACISetSession Azure Functions,以更新玩家被指派连接的容器上运行的活动会话数量。
  12. ACISetSession Function 更新 Azure 表存储。
  13. 到某个时刻后,将不再需要服务器。 由于玩家可能仍在使用容器实例,因此本例不会手动删除容器,而是调用 ACISetState Azure Functions 来将容器的状态设置为 MarkedForDeletion,这样一来,新玩家就不会被安排到该容器实例。
  14. ACISetState Azure Functions 更新 Azure 表存储。
  15. 时间触发的 ACIGC Azure Functions 会时不时地运行,并删除所有具有 MarkedForDeletion 状态的容器实例。
  16. ACIGC Azure Functions 调用可实际删除容器实例的 ACIDelete Azure Functions。
  17. ACIDelete Azure Functions 删除容器。

使用更具体的示例:

Azure 容器实例工作流

  1. 一开始没有服务器实例。
  2. 突然,玩家需要服务器进行连接,系统调用了 ACICreate
  3. 执行创建命令时,服务器尚未启动和运行, 状态为 Creating
  4. 完成部署后(几分钟),事件网格将通过 ACIMonitor 通知您服务器实例(容器组 1)正在特定的 IP 地址(即 1.2.3.4)中运行。 系统将该实例的状态更新为 Running
  5. 玩家现在可以连接到服务器实例。
  6. 假设现在需要另一个服务器实例。 您可以再次使用 ACICreate,或者如果已超出横向扩展阈值,则让 ACIAutoScaler 代表您使用 ACICreate 创建实例。
  7. 还是同样的模式,新服务器尚未准备就绪,当部署完成后,事件网格将通过 ACIMonitor 通知您服务器在另一个特定 IP 地址(即 2.3.4.5)中运行。 系统将这第二个实例(容器组 2)的状态更新为 Running
  8. 玩家现在可以连接到第二个实例。
  9. 最后,不需要第二个实例,我们决定不使用它或者超出了扩展阈值。 由于可能有玩家仍在使用第二个实例,因此系统不会删除该实例,而是调用 ACISetScale 将服务器标记为删除,这样一来,新玩家就不会被安排到第二个实例。 现在,第二个实例的状态是 MarkedForDeletion
  10. 当玩家在第二个实例中结束游戏后,系统将运行垃圾回收器 ACIGB,并触发 ACIDelete 以完全删除第二个实例。

缩放

可通过以下环境变量配置 ACIAutoScaler Azure Functions 设置:扩展/收缩阈值、最小/最大实例数以及冷却。 下面是自动缩放器的逻辑:

  • 计时触发,每隔 1 分钟运行一次,默认禁用。
  • 负载为已连接的玩家数量 / 总玩家容量
  • 如果(“负载”> 80% 且实例 < 最大实例数),则调用 ACICreate Azure Functions 以加快启动新实例。 这可以处理好扩展情形。
  • 如果(“负载”< 60% 且实例 > 最小实例数),则调用 ACISetState Azure Functions,将负载最小的实例的状态设置为 MarkedForDeletion。 这可以处理好收缩情形。
  • 扩展/收缩有 10 分钟的冷却期。

Azure 容器实例可以快速实现扩展,只需几分钟,您便可以随时开始使用新容器。 例如,对于扩展 30 个运行 OpenArena 的新 Azure 容器实例的请求,从请求发出到玩家能够连接,用时不到 3 分钟。

手动创建一组容器进行测试

如果您想创建一组容器实例进行测试,可以使用此处提供的示例。 将 openarenaserver1 替换为您要创建的各个实例的唯一名称(如 openarenaserver1、openarenaserver2、openarenaserver3 等)。 您还可以替换资源组、位置和存储名称/键。

其他资源和示例

请观看 Build 上录制的使用 Azure 容器实例实现多人游戏服务器缩放 视频,了解详细信息。

定价

如果您没有 Azure 订阅,可以创建免费帐户,开始使用 12 个月的免费服务。 除非您超出这些服务的使用限制,否则无需为 Azure 免费帐户中包含的这些免费服务付费。 了解如何通过 Azure 门户使用情况文件查看服务使用情况。

您需要承担运行这些参考体系结构时使用的 Azure 服务的费用,总金额取决于将通过分析管道运行的事件数。 请参阅参考体系结构中使用的每项服务的定价网页:

您还可以使用 Azure 定价计算器,以配置和估算您计划使用的 Azure 服务的成本。