跳至主要内容

仲裁队列和流量控制 - 概念

·阅读 8 分钟
Jack Vanlightly

作为我们仲裁队列系列的一部分,我们将深入探讨流量控制,它如何保护 RabbitMQ 免受过载的影响,以及这与仲裁队列的关系。

什么是流量控制?

流量控制是一个在计算机网络和网络软件中存在了几十年的概念。从本质上讲,它是一种机制,用于对发送方施加反压,以避免接收方过载。接收方通常会缓冲传入的数据包/消息,以此来处理发送速率超过其处理速率的情况。但是接收方缓冲区不能无限增长,因此要么发送速率只能短暂地超过接收方处理能力(突发流量),要么必须降低发送方的速度(反压)。

流量控制是一种对发送方施加反压的方法,降低其速度,以使接收方的缓冲区不会溢出,并且延迟不会变得过大。在一个发送方/接收方的链中,这种反压可以向上游传播到流量的源头。在更复杂的互连组件图中,流量控制可以在快速和慢速发送方之间平衡传入流量,避免过载,但允许系统即使在发送方数量不同、速率不同和负载模式不同(稳定或突发)的情况下也能达到充分利用。

RabbitMQ 中的流量控制

RabbitMQ 看起来很像一个网络。每个 RabbitMQ 代理在内部使用 actor 模式实现,其中不同的组件通过消息传递相互通信,有时在本地,有时通过网络。还有发送消息到代理的发布者和从代理接收消息的消费者,两者都通过网络进行。

Fig 1. A simplified depiction of message flows
图 1. 消息流的简化描述

在这个链中的任何一点,都可能发生瓶颈导致拥塞。拥塞会导致缓冲区和端口填满,磁盘上的数据增长以及硬件资源减少。如果无人控制,RabbitMQ 可能会耗尽所有可用内存,导致崩溃或被操作系统杀死。

将整个系统(代理和客户端)作为一个整体,我们有四种可用的流量控制机制

  • 基于信用的流量控制
  • 内存警报
  • 发布者确认
  • 消费者确认和预取

基于信用的流量控制

基于信用的流量控制 是一种限制消息进入速率的方法。它允许系统中的各种 actor 保护自身并在无法快速处理消息时施加反压。它只针对那些遇到问题的连接、通道和队列,而不会影响系统的其他部分。

它的工作原理是,系统中处理消息的每个 actor 都使用“信用”作为一种在链上施加反压的方式。如果一个通道想要向一个队列发送消息,它需要信用。队列会向通道授予一些初始信用,然后,通道向队列发送的每条消息都需要一个信用。队列会在它能够将消息传递到持久化层后定期向通道授予更多信用。如果通道没有信用,它将被阻止向队列发送消息,直到队列向它授予更多信用。这样,通道就不能肆意占用队列。

Fig 2. Credit based flow control with classic queues
图 2. 使用经典队列的基于信用的流量控制

因此,我们有一系列信用流控制,它们将反压一直施加回发布者。最终,TCP 反压将应用于发布者,因为 TCP 读取器进程没有从套接字读取,因为它被阻塞了。

当连接、通道或队列用完信用时,它们会被阻塞,直到授予更多信用,这种状态称为“流”。在管理 UI 中,您可能会看到连接、通道或队列处于 *流* 状态,这表示最近发生了流。这仅仅意味着它们暂时用完了信用,并且正在等待链中的下一个环节赶上来并授予一些信用。这每秒可能会触发多次。

Fig 3. Credit exhaustion.
图 3. 信用耗尽。

Fig 4. Credit grants.
图 4. 信用授予。

当队列或连接达到其吞吐量限制或下游瓶颈时,流状态可能会每秒触发多次,在链中的各个点,因为各种 actor 的信用数量达到 0 然后得到补充。

但这并不一定能阻止代理耗尽内存。传入消息并不总是高内存使用率的主要原因,它也可能来自大型队列和许多其他原因。

内存警报

如果基于信用的流量控制无法充分刹车,或者由于其他原因导致内存使用量增长到临界水平,则内存警报作为最后手段启动,以保护代理免于因内存不足而崩溃(或被操作系统杀死)。

内存警报 启动时,所有发布者都会被阻止。这就像您关闭了整个集群传入消息的水龙头。不是基于信用的流量控制的有针对性的速率限制,而是一锤定音。

但是消费者可以继续消费,此时希望通过某种程度上的队列清空来开始降低内存占用。

在管理 UI 中,当内存警报生效时,您会看到连接被阻止或正在阻止。

发布者确认

发布者确认 的主要工作是数据安全,但它们在流量控制中也扮演着重要角色。

有三种方法可以采用发布者确认

  • 一次发送一条,在发送下一条之前等待每个确认(非常慢)。
  • 基于窗口。发送消息直到达到窗口大小(时间或消息数量),并在发送下一个窗口之前等待所有确认。
  • 流水线。允许发布者连续发送消息,但在未确认的消息数量(正在传输的消息)达到限制时阻塞。当确认到达时,可以发送更多消息,直到再次达到限制。

流水线(或简单的 *异步*)方法提供了最高和最稳定的吞吐量。它可以用作防止代理过载的额外保护,因为发布者本身在对代理施加压力之前就将自身置于“流”状态。

当您不使用发布者确认时,您完全依赖于发布者和代理上的连接读取器进程之间的 TCP 流量控制。对于数量相对较少的发布者,TCP 流量控制足以避免代理过载,但是当您有大量客户端时,TCP 就足够了,并且发布者确认在负载过重时对于集群稳定性变得至关重要。有趣的是,AMQP 1.0 添加了链接流量控制来克服这个问题。

消费者确认和预取

使用手动确认和预取会对 RabbitMQ 施加反压,以阻止它压垮您的消费者客户端。它使用流水线方法发送恒定的消息流,但将未确认消息的数量限制为预取(QoS)的大小。使用 AutoAck 模式,我们再次仅依赖 TCP 反压。客户端的各种输入缓冲区可能会很快填满。

强烈建议使用手动确认和预取。

仲裁队列

基于信用的流量控制链在仲裁队列中有所不同,因为它们具有完全不同的持久性和复制协议。通道将所有消息发送到 Raft 集群领导者,然后领导者将消息传递到 WAL 并将其复制到其跟随者。仲裁队列用于复制的 Raft 共识算法不包括基于信用的流量控制,因此此机制在通道处结束。

通道不是在 Raft 领导者无法跟上时使用信用作为阻塞自身的机制,而是跟踪领导者尚未应用的未决 Raft 命令的数量。如果该数量超过 *仲裁命令软限制* 配置,则通道将停止向读取器进程授予更多信用。

Fig 5. Credit based flow control in quorum queues.
图 5. 仲裁队列中的基于信用的流量控制。

其他控制(如内存警报、发布者确认和消费者确认/预取)对于仲裁队列和经典队列来说是相同的。

即将发布的 3.8.4 版本中对仲裁队列的改进

我们发现流量控制是仲裁队列与镜像队列相比较薄弱的领域之一。我们发现,在来自数百或数千个客户端的重负载下,仲裁队列没有像镜像队列那样有效地应用反压。仲裁队列在这些情况下出现了一些新的瓶颈,这些瓶颈会导致内存增长,依赖内存警报而不是基于信用的流量控制。

我们着手改进下一个版本的此问题,并且已经极大地改进了仲裁队列的这一方面,我们将在 3.8.4 发布时详细介绍这些更改。

在下一部分… 使用发布者确认来提高负载下的吞吐量

因此,您对 RabbitMQ、流量控制和仲裁队列有了更多的了解。您知道发布者确认对于数据安全和发布者与代理之间的流量控制都很重要。但是我们如何实现流水线?我们应该使用多大的未完成限制?不同的限制对吞吐量和延迟会有什么影响?

所有这些问题都将在本系列的下一部分中得到解答。我们将对镜像队列和仲裁队列运行一系列基准测试,并获得一些答案。

© 2024 RabbitMQ. All rights reserved.