跳到主要内容

分区交接

每次代理执行分区再平衡时,都会有一个过程需要将一个或多个分区重新分配给不同的消费者(流程)。这被称为分区交接。代理尝试尽可能少地中断地执行分区交接,即“优雅”的分区交接。优雅并不一定是完美的;根据消费者应用程序的行为,可能会中断消息流。下面讨论了消费者应用程序的最佳实践。

优雅的分区交接过程尝试实现以下目标(按最理想到最不理想的顺序列出):

  • 目标1:完全防止中断。这是最理想的,但需要消费者特定行为。见下文消费者应用程序的最佳实践。
  • 目标2:将中断限制在消费者正在改变的分区,使其他分区不受影响。
  • 目标3:将中断限制在映射到分区的关键序列的一个子集。“关键序列”一词指的是必须顺序处理完成的一系列具有相同键的消息。事件代理不知道关键序列的开始和停止(即,长度)。

让我们考虑一个示例,说明分区交接所涉及的因素。假设我们有一个由多个微服务(发布者和消费者)组成的应用程序。如下图所示,应用程序最初有一个发布者和一个消费者。消息由字母A和B表示(代表分区键;例如,客户编号)和数字1到5表示(代表键内的序列;例如,发票编号)。有一个单一的分区队列,有两个分区,分配给消费者1。假设新消费者(消费者2)变得可用,因此需要将分区1消费者1交接给消费者2。在这个示例中,分区0以任何方式都不受影响(满足目标2);只有与键B相关的消息受到影响。

img

从事件代理的角度来看,消息B1到B5的状态如下:

  • B1已交付给流程但尚未确认,因此保留在分区中以备潜在重新交付。(消费者1客户端已接收B1并正在处理中。)

  • B2已交付给流程但尚未确认,因此保留在分区中以备潜在重新交付。(消费者尚未接收B2。)

  • B3已接收但尚未交付给任何流程。

  • B4对代理来说是未知的,因为它正在从发布者那里传输。

  • B5对代理来说是未知的,因为发布者尚未发送它。

优雅的分区交接包括以下步骤:

  1. 事件代理暂停从受影响分区到当前流程的新消息交付(它们保留在队列中)。
    • 在示例中,分区1中的B3消息被保留在队列中,并未发送给消费者1的流程。队列接收到它们后,B4和B5消息也被保留并未发送给流程。
  2. 如有必要,事件代理等待配置的rebalance max-handoff-time(以便消费者确认未完成的消息)。
    • 在示例中,消费者1确认消息B1和B2。
  3. 事件代理更新分区到流程映射,以便新流程被分配给受影响的分区。
    • 在示例中,消费者2的流程现在被分配给分区1
  4. 事件代理恢复交付,现在包括新流程。
    • 在这里,B3、B4和B5被交付给消费者2

假设所有消息现在都已被确认,交接的结果如下图所示:

img

我们的示例应用程序可能期望所有消息B1到B5都被交付给同一个消费者,以便在处理后续消息时可以使用早期消息的状态。这是分区队列的正常行为,但由于交接,B1和B2被发送给消费者1,而B3到B5被发送给消费者2。也就是说,在某个点上,B的关键序列被中断了。

需要注意的是,只有在关键序列有未交付的消息时,才会发生关键序列的中断。示例中的特定序列(B3-B5)被中断,但可能只是正在重新分配的分区中许多独立关键序列之一。同一个发布者或任何其他发布者可能正在生成完整的关键序列,其消息正在传输给消费者,或者尚未被任何消费者看到。在暂停交付后,这些消息在不中断其关键序列的情况下被处理(满足目标3)。

消费者应用程序的最佳实践

理想情况下,分区交接对消费应用程序是透明的。尽管交接被设计为尽可能不显眼地对消费者进行处理,但它们可能会导致应用程序级别的错误,因为属于一个分区的消息流从一个消费者转移到另一个消费者。

可以设计您的消费应用程序,使其能够抵御分区交接导致的任何消息流中断(目标1)。实现这一点的一些可能方式包括:

  • 使用共享数据库维护状态。前面示例的图表显示了一个所有消费者都可以从中读取和写入的数据库。如果应用程序的每个实例在确认回代理的相应消息之前,将给定键所需的任何状态写入数据库,那么应用程序的任何其他实例都可以通过首先从共享数据库检索此键的状态来处理它以前未遇到过的键的消息。
  • 在接收到序列中的最后一个消息后才确认。如果应用程序知道一个键序列的开始和结束值,它可以延迟发送对序列中所有消息的确认,直到收到并处理了最终消息。这样,如果序列被干扰,事件代理将把整个序列按顺序重新发送给新消费者。这种方法不会消除问题,但它减少了应用程序暴露的窗口大小(交接仍可能在连续确认所有消息所需的时间内发生)。

可能无法或不切实际设计如此复杂的应用程序。特别是,一些设计决策可能导致分区键序列中断。例如:

  • 消费者可能仅在RAM中保存其每个键的状态,并自动确认每条消息,以便尽可能简单和高性能。

  • 消费者可能不会立即确认消息,而是根据消息序列更有选择性地确认,从而超过队列的重新平衡计时器或最大交接计时器。 消费者可能无法在配置的时间限制内确认消息。这可能导致消息重复。可以调整max-delivered-unacked-msgs-per-flow设置(类似于您将为其他非独占队列场景所做的调整,这些场景中不同消息工作负载的处理时间不同)以防止这种情况。

  • 管理员可能将超时值设置得很低,甚至为0,以便在新消费者中处理新的关键序列,而不是尝试优雅地完成旧消费者中旧关键序列的处理。

  • 消费者可以设计为失败并重新启动事务或流程。

在这些情况下的恢复方法取决于应用程序。您必须决定当接收到的消息的关键序列永远不会完成,或者当它们被赋予的键不从关键序列的开始时,您的应用程序将如何表现。