如何扩展 NodeJS 有状态应用程序

How to scale a NodeJS stateful application

我目前正在开发一款基于网络的 MMORPG 游戏,我想设置一个基于 Docker 和 DigitalOcean droplets 的自动缩放策略。

但是,我想知道如何才能做到这一点:

我的游戏服务器必须可以拆分到不同的 Docker 容器中 但是 每个游戏服务器实例都应该像只有一个巨大的游戏服务器一样运行。这意味着在一个服务器中发生的每个修改(角色移动)也应该在所有其他游戏服务器中得到镜像。

我正在尝试让它工作(至少在概念上),但找不到正确同步所有实例的方法。我应该只使用主广播事件还是有其他选择?

我想知道我的 MySQL 数据库也有同样的问题:因为每个游戏服务器都必须 read/write from/to 数据库,我该如何使它随着游戏的进行而适当扩展越来越大?我能想到的最佳解决方案是将数据库保存在一台服务器上,这将非常强大。

我知道如果所有游戏服务器都不必 "share" 它们的状态,这可能很容易,但这主要是为了让我可以在 [=35= 突然飙升的情况下快速扩展].

(将会有不同的 "global" 游戏服务器,例如 A、B、C...但是这些全球游戏服务器中的每一个都应该在幕后由 1-X docker容器 运行 "real" 游戏服务器 所以 "global" 游戏服务器只是一个概念)

你的问题太宽泛了,正如其他人提到的那样,这是一个普遍的缩放问题。如果您能更清楚地说明您的系统要求是什么,将会很有帮助。

如果它必须是实时的,那么您可以选择 Redis 作为您的主数据库,但是您需要从属数据库(用于复制)并且您将无法在运行时自动扩展*,因为 Redis不支持那个。我认为当你玩游戏时这不是一个好的选择(可能会出现突然的峰值)

*似乎有一些托管解决方案,您需要查看它们

如果可以接近实时,使用 Apache Kafka 可以证明是有用的。

还有一个高度可扩展的数据库,其中包含您需要的一切,称为 CockroachDB我是贡献者,耶!)但您需要 运行 测试是否满足您的延迟要求。

总体而言,使用非常 强大的服务器是一个糟糕的选择,因为存在上限并且垂直扩展会花费更多。

你说的问题太笼统了,很难给出具体的答复。然而,让我鲁莽地给你一些通用的缩放建议:

  • 从数据库中删除计数器。而不是自动递增 ID 的主键,尝试分配随机 UUID。

  • 更改必须通过自包含数据针对中心点进行验证的数据。例如,对于身份验证,不要使用数据库中的用户凭据,而是使用可以由任何主机验证的 JSON Web 令牌。

  • 使用一致性哈希等技术来平衡负载,而不需要负载平衡器。当然使用分布良好的散列函数,avoid/minimize 冲突。

以上建议基本上是关于更改设计以尽可能多地从有状态迁移到无状态。如果您无论如何都需要提供有状态的部分,请尝试猜测哪些实体将有更多机会共享有状态数据并将它们分配到相同(或接近服务器)中。例如,如果您的游戏中有多个城市,请尝试将同一城市的用户分配到同一服务器中,因为与不同城市的用户相比,他们更愿意在他们之间进行交互(并共享状态数据)。

当然,如果城市太大,非常拥挤,您可能需要将城市划分为更多的服务器,以避免服务器过载。

横向扩展此类应用程序有很大好处。我会试着写下一些想法。

选项 1(有状态):

在规划有状态的应用程序时,您需要注意状态的同步(通过 PubSub、网络广播或其他方式)并注意每次同步都需要时间才能发生(如果不阻塞每个操作)。如果这对你来说没问题,让我们继续吧。

假设您在整个集群上每秒有 80k 次操作。这意味着每个进程每秒需要同步 80k 的状态变化。这将是你的瓶颈。每秒处理 80k 次更改对于 Node.js 应用程序来说是一个巨大的挑战(因为它是单线程的,因此会阻塞)。

最后,您需要准确地提供您希望能够同步的最大更改量,并使用不同的编程语言执行一些测试。同步的开销需要添加到应用程序的一般工作负载中。使用 C、Java/Scala 或 Go 等多线程语言可能会有所帮助。

选项 2(有状态路由):*

在某些情况下,实施不同类型的缩放是可行的。 例如,当您的应用程序可以分解为地图区域时,您可以从一个包含完整地图的应用程序复制开始,当它按比例放大时,它会按比例共享地图。 您需要在应用程序服务器之间实现一些路由,例如更改世界 B 的城市 A 中的状态 => 调用服务器 xyz。这可以自动完成,但缩小规模将是一个挑战。

此解决方案需要更多关注和了解应用程序,并且不如选项 1 容错,但它可以无限扩展。

选项 3(无状态):

将状态转移到其他应用程序并在其他地方解决问题(如 Redis、Etcd 等)