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


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

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

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

我试图让它工作(至少在概念上),但无法找到一种方法来正确同步我的所有实例。我应该只使用主设备广播活动还是有替代方案?

我想知道我的MySQL数据库是一样的:因为每个游戏服务器都必须从/向db读/写,所以当游戏变得越来越大时,我如何才能使它正确缩放?我能想到的最好的解决方案是将数据库保存在单个服务器上 非常 强大。

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

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


10850
2018-05-15 15:35


起源

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


答案:


你说的问题太笼统了,很难给出具体的回应。但是,让我肆无忌惮,给你一些通用的扩展建议:

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

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

  • 使用Consistent Hashing等技术来平衡负载而无需负载平衡器。当然,使用散列函数可以很好地分配,以避免/最小化冲突。

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

当然,如果城市太大而且非常拥挤,您可能需要在更多服务器中对城市进行分区以避免服务器过载。


5
2018-05-19 14:39





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

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

*似乎有一些托管解决方案,你需要检查出来

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

还有一个高度可扩展的数据库,它拥有您需要的所有内容 CockroachDB (我是贡献者,耶!)但您需要运行测试以查看它是否符合您的延迟要求。

总的来说,一起去 非常 功能强大的服务器是一个糟糕的选择,因为有一个上限,垂直扩展会花费更多。


4
2018-05-18 08:49



我的想法是为我的游戏服务器实例提供节点。并且他们将使用一个或多个redis实例同步自己,以便状态包含在redis中,我可以更轻松地扩展我的工作者 - Telokis
你可以使用Redis中的pub / sub和key来实现这一点,但是一旦你的req / sec变得太大,你就会遇到你的数据库瓶颈。您不仅需要扩展node.js实例(以服务请求),还需要扩展后端,这是您的数据库! - d9ngle
是的,这就是问题所在。至少有redis集群看起来相当不错,即使我还没玩过它。否则,我可以在水平扩展我的nodejs,socket.io实例的同时垂直扩展redis。 - Telokis
Redis群集使用散列槽,因此当您向群集添加新节点时,需要重新分片,因此它可能不是自动扩展的最佳解决方案。 (如果是尖峰)你也不能表演 MULTI 在生活在不同节点上的键上。如果你需要强大的一致性和交易,cockroachdb是一个明智的选择! - d9ngle
是的,这似乎相当快,但在速度方面它与Redis相比如何呢?由于Redis代表RAM,它更像是一个缓存。也许我甚至可以结合两者? - Telokis


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

备选方案1(有状态):

在规划有状态应用程序时,您需要注意状态的同步(通过PubSub,网络广播或其他方式),并注意每次同步都需要一段时间(不阻止每个操作)。如果你没问题,那就继续吧。

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

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

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

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

此解决方案需要更多关注和应用程序的知识,并不像选项1那样具有容错能力,但它可以无限扩展。

选项3(无国籍):

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


2
2018-05-24 19:24