在群集环境中使用消息队列进行长轮询

Long-polling with message queues in a clustered environment

提问人:user1597121 提问时间:7/10/2023 更新时间:7/11/2023 访问量:36

问:

我有一个系统设计问题,我正在寻找一些指导。我有两个不同的系统,需要有一个基本的通信水平。这是通过消息队列抽象的。

例如,系统 A 负责用户帐户注册。用户访问系统B,进入用户注册流程。用户提交详细信息后,系统 B 将发送至系统 B 的“加载/等待”屏幕,并向系统 B 注册一个长轮询请求,等待其帐户的完成。在后端,系统 B 将包含注册详细信息的消息放入消息队列中。系统 A 使用此消息,创建帐户,然后将一条消息放入单独的队列中,指示注册已完成。系统 B 使用此消息,并且需要通知用户注册已完成。这就是问题所在。系统 B 正在运行服务器群集,并且无法保证保留长轮询请求的服务器是处理注册完成消息的同一台服务器。

这似乎应该是一个常见的问题,但我没能找到太多关于它的讨论。我已经探索了一些选择,但它们似乎都有一定程度的缺点。

可能的解决方案:

  1. 当系统 B 使用注册完成消息时,使用类似 JGroups 的东西将其广播到集群中的所有其他服务器
  2. 每个实例动态创建自己的 SQS 队列,并使用 SNS 扇出模式将消息传送到所有这些队列,以便所有服务器都收到消息的副本
  3. 跟踪请求所在的服务器,并找到一种方法将消息路由到适当的服务器(这尤其棘手,因为在路由过程中,请求超时,同一用户的另一个请求由不同的服务器发起和处理)
  4. 只需使用标准轮询来消除服务器状态方面

非常感谢对此问题的任何见解或建议。谢谢。

集群计算 消息队列 长轮询

评论


答:

0赞 Stephen Cleary 7/11/2023 #1

任何长期连接(包括长轮询和 WebSocket)的一个注意事项是服务器池需要能够回收。例如,在滚动更新或服务器更换期间,客户端和服务器应用程序都需要能够处理丢失的长期连接。

更详细的示例:

  • 在服务器端部署(滚动更新)期间,必须从池中删除、关闭、更新、启动每台服务器,然后将其添加回池中。这将断开该服务器的所有长期连接。客户端应用程序应自动重试并重新建立其连接(理想情况下应允许连接到其他服务器)。
  • 在服务器更换期间,将从池中删除一台服务器,并添加另一台服务器。这意味着架构不能对长期存在的服务器做出假设;相反,所有服务器都被视为单个群集。这也允许向上和向下扩展。

这通常是通过(通常是静默的)对长期连接丢失的重试来完成的。在某些情况下,随着时间的推移,可以在多个文本连接上存在“逻辑连接”抽象。例如,Microsoft的SignalR采用了这种方法。

这种方法是我唯一拒绝的方法:

跟踪请求所在的服务器,并找到将消息路由到相应服务器的方法

因为它单独处理服务器,而不是将其视为集群。

就个人而言,在大多数情况下,我更喜欢轮询方法。我还使用过扇出的每服务器队列,但仅适用于每个服务器实际上对每条消息都有事要做的情况(即,某些状态被复制到所有服务器),并且构建 API 作为这些消息的单一事实点效率太低。这种情况很少见,我几乎总是使用轮询来代替。

关于其他解决方案:

当系统 B 使用注册完成消息时,使用类似 JGroups 的东西将其广播到集群中的所有其他服务器

每个实例动态创建自己的 SQS 队列,并使用 SNS 扇出模式将消息传送到所有这些队列,以便所有服务器都收到消息的副本

我不熟悉 JGroups,但如果广播可以丢失,那么我不会采取第一种方法。

对于任何类型的消息传递(广播和每服务器队列),还有一个问题,例如,如果客户端断开连接(如果服务器从池中取出),消息将由所有服务器发送和处理,然后客户端重新连接。在这种情况下,消息会丢失吗?

您可以通过将所有服务器上的所有消息缓存一段时间(并在相关超时后禁止客户端自动重新连接)或让系统 B 调用系统 A 来处理这种情况 - 在这种情况下,您刚刚实现了轮询所需的 API。

只需使用标准轮询来消除服务器状态方面

我的首选解决方案。服务器(在此级别)是一个集群,而不是单个服务器。