"Resequencing" 条乱序处理后的消息

"Resequencing" messages after processing them out-of-order

我正在研究基本上是一个高度可用的分布式消息传递系统。系统通过 HTTP 或 TCP 从某处接收消息,对其执行各种转换,然后将其发送到一个或多个目的地(也使用 TCP/HTTP)。

系统要求所有发送到给定目的地的消息都是按顺序发送的,因为有些消息建立在之前消息的内容之上。这限制了我们按顺序处理消息,每条消息大约需要 750 毫秒。因此,如果有人每 250 毫秒向我们发送一条消息,我们就被迫将这些消息排在彼此后面。这最终会在高负载下的消息处理中引入无法忍受的延迟,因为每条消息可能必须等待数百条其他消息被处理才能轮到它。

为了解决这个问题,我希望能够在不破坏按顺序发送它们的要求的情况下并行处理我们的消息。

我们可以很容易地水平扩展我们的处理。缺少的部分是一种确保即使消息被乱序处理,它们 "resequenced" 并按照接收顺序发送到目的地的方法。我正在努力寻找实现该目标的最佳方法。

Apache Camel 有 a thing called a Resequencer 可以做到这一点,它包括一个很好的图表(我没有足够的代表直接嵌入)。这正是我想要的:接收无序消息并将它们按顺序排列的东西。

但是,我不希望它被写在 Java 中,我需要解决方案具有高可用性(即抵抗典型的系统故障,如崩溃或系统重启),我不这样做认为 Apache Camel 提供。

我们的应用程序是用 Node.js 编写的,使用 Redis 和 Postgresql 进行数据持久化。我们将 Kue 库用于我们的消息队列。尽管 Kue 提供优先级队列,但功能集对于上述用例来说太有限了,所以我认为我们需要一种替代技术来与 Kue 协同工作来重新排序我们的消息。

我试图在线研究这个主题,但找不到我预期的那么多信息。似乎分布式架构模式的类型会有大量的文章和实现,但我没有看到那么多。搜索 "message resequencing"、"out of order processing"、"parallelizing message processing" 等内容会找到解决方案,这些解决方案大多只是根据分区或主题或诸如此类的东西放宽 "in-order" 要求。或者,他们谈论单台机器上的并行化。我需要一个解决方案:

我们目前的计划(这对我来说很有意义,但我无法在网上的任何地方找到描述)是使用 Redis 来维护一组正在进行的和准备发送的消息,并按它们的到达时间排序。大致来说,它是这样工作的:

  1. 收到消息后,该消息将被放入正在进行的集。
  2. 消息处理完成后,该消息将被放入准备发送集。
  3. 只要在进行中和准备发送集的前面有相同的消息,就可以发送该消息并且它会按顺序发送。

我会编写一个小型 Node 库,使用原子 Redis 事务通过优先级队列式 API 实现此行为。但这只是我自己想出的东西,所以我想知道:是否有其他技术(最好使用我们已经使用的 Node/Redis 堆栈)可以解决重新排序的问题-订购信息?或者这个问题是否有其他术语可以用作研究的关键词?感谢您的帮助!

这是一个常见问题,所以肯定有很多解决方案。这也是一个相当简单的问题,也是分布式系统领域的一个很好的学习机会。我建议您自己编写。

你在构建这个时会遇到一些问题,即

2: Exactly-once delivery
1: Guaranteed order of messages
2: Exactly-once delivery

您找到了第 1 个,您正在通过在 Redis 中对它们重新排序来解决这个问题,这是一个不错的解决方案。然而,另一个没有解决。

看起来您的体系结构不适合容错,因此目前,如果服务器崩溃,您可以重新启动它并继续您的生活。这在按顺序处理所有请求时效果很好,因为根据上次成功完成的请求,您可以准确知道崩溃的时间。

您需要的是找出您实际完成了哪些请求以及哪些请求失败的策略,或者是在出现问题时向客户发送一封写得很好的道歉信。

如果Redis不分片,就是强一致性。如果单个节点崩溃,它将失败并可能丢失所有数据,但是您不会遇到乱序数据或数据突然出现和消失的任何问题。因此,单个 Redis 节点可以保证,如果一条消息被插入到待处理集中,然后再插入到完成集中,则没有节点会在完成集中看到该消息,除非它也在待处理集中。过程集。

我会怎么做

使用 redis 似乎太模糊了,假设消息不是很大,如果进程崩溃,丢失它们是可以的,并且 运行 它们不止一次,甚至是多个副本同时单个请求不是问题。

我建议设置一个主管服务器,它接收传入的请求,将每个请求分派给随机选择的从服务器,存储响应并在发送它们之前将它们重新按顺序排列。你说你预计处理需要 750 毫秒。如果从站在 2 秒内没有响应,则在 0-1 秒内将其再次随机发送到另一个节点。第一个响应的是我们将要使用的那个。谨防重复回复。

如果重试请求也失败,则将最长等待时间加倍。在 5 次左右的失败之后,每次等待的时间都是前一次的两倍(或任何大于一的倍数),我们可能会遇到永久性错误,因此我们可能应该请求人为干预。该算法称为指数退避,可防止请求突然激增导致整个集群崩溃。不使用随机间隔,并在 n 秒后重试可能会导致每 n 秒一次 DOS 攻击,直到集群死亡,如果它获得足够大的负载峰值。

失败的原因有很多种,因此请确保该系统不是唯一存储数据的地方。然而,这可能在 99% 以上的时间都有效,它可能至少与您当前的系统一样好,并且您可以用几百行代码来实现它。只需确保您的主管正在使用异步请求,以便您可以处理重试和超时。 Javascript 本质上是单线程的,所以这比正常情况稍微复杂一些,但我相信你能做到。