【问题标题】:How to scale a NodeJS stateful application如何扩展 NodeJS 有状态应用程序
【发布时间】:2017-05-15 15:35:42
【问题描述】:

我目前正在开发一款基于网络的 MMORPG 游戏,并希望设置基于 Docker 和 DigitalOcean 液滴的自动缩放策略。

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

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

我试图让它工作(至少在概念上),但找不到正确同步所有实例的方法。我应该使用仅广播事件的主节点还是有替代方法?

我想知道关于我的 MySQL 数据库的同样的事情:因为每个游戏服务器都必须从数据库读取/写入数据库,随着游戏变得越来越大,我该如何使其正确扩展?我能想到的最佳解决方案是将数据库保存在一个非常强大的服务器上。

我知道,如果所有游戏服务器都不必“共享”它们的状态,这可能会很容易,但这主要是为了在活动突然激增的情况下我可以快速扩展。

(会有不同的“全局”游戏服务器,如 A、B、C……但这些全局游戏服务器中的每一个都应该在幕后由运行“真实”游戏服务器的 1-X 个 docker 容器组成所以“全球”游戏服务器只是一个概念)

【问题讨论】:

  • 这似乎是一个普遍的缩放问题。无论是否在 Docker 中,问题都是一样的,对吗?
  • 对,但我认为 docker 在轻松部署方面几乎是一种开箱即用的解决方案。这就是为什么我可能会用它来托管我的游戏服务器。
  • 运行另一个 Docker 容器类似于运行另一个进程。它不会为您的应用程序处理状态或内存复制。应用程序仍然需要通过支持多个主机的某种总线或协议在多个进程之间实现共享状态。
  • 这样的问题就是为什么魔兽世界有区域,而夏娃有系统被网格分割的原因。很可能,您需要破坏您的游乐区。
  • 支持上面@Stephan 的评论,在当前的 MMO 中,每个“领域”或“服务器”都由多个(自动)扩展服务器组成,这些服务器能够服务于一个或多个较小的区域,通常用于玩家位置/运动和战斗。

标签: node.js docker scalability docker-swarm


【解决方案1】:

你说的问题太笼统,很难给出具体的回应。但是,让我鲁莽地给你一些通用的扩展建议:

  • 从数据库中删除计数器。相反,作为自动递增 ID 的主键,尝试分配随机 UUID。

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

  • 使用一致性哈希等技术来平衡负载,而无需负载平衡器。当然要使用分布良好的散列函数,以避免/最小化冲突。

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

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

【讨论】:

    【解决方案2】:

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

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

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

    如果它可以接近实时,那么使用 Apache Kafka 会被证明是有用的。

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

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

    【讨论】:

    • 我的想法是为我的游戏服务器实例创建节点。他们会使用一个或多个 redis 实例同步自己,以便状态包含在 redis 中,我可以更轻松地扩展我的工作人员
    • 您可以在 Redis 中使用 pub/sub 和键来完成此操作,但是一旦您的请求/秒变得太大,您就会遇到数据库瓶颈。您不仅需要扩展 node.js 实例(以服务请求),还需要扩展后端,即您的数据库!
    • 是的,这就是问题所在。至少有一个 redis 集群,尽管我没有玩过它,但它看起来很不错。否则,我可以在水平扩展我的 nodejs、socket.io 实例的同时垂直扩展 redis。
    • Redis 集群使用哈希槽,因此当您向集群添加新节点时,您需要重新分片,因此它可能不是自动扩展的最佳解决方案。 (如果出现峰值)您也不能对位于不同节点上的键执行MULTI。如果您需要强一致性和事务,那么 cockroachdb 是不二之选!
    • 是的,这看起来相当快,但它与 Redis 相比在速度方面如何?由于 Redis 位于 RAM 中,因此它更像是一个缓存。也许我什至可以将两者结合起来?
    【解决方案3】:

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

    选项 1(有状态):

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

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

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

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

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

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

    选项 3(无状态):

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

    【讨论】:

      猜你喜欢
      • 2015-05-23
      • 2018-06-07
      • 1970-01-01
      • 2015-12-04
      • 2012-04-04
      • 1970-01-01
      • 1970-01-01
      • 2016-07-31
      • 1970-01-01
      相关资源
      最近更新 更多