跳至主内容

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

·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 读取器进程由于被阻塞而无法从套接字读取。

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

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

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

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

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

内存告警

如果基于信用的流量控制未能足够有效地刹车,或者由于其他原因内存使用已增长到关键水平,内存告警将作为最后的手段启动,以保护代理免于因内存耗尽而崩溃(或被操作系统终止)。

内存告警启动时,所有发布者都会被阻塞。这就像您关闭了整个集群的传入消息的水龙头。这不像基于信用的流量控制那样有针对性的速率限制,而是一种“一刀切”的方法。

尽管如此,消费者仍然可以继续消费,此时的希望是通过消耗一些队列来降低内存占用。

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

发布者确认

发布者确认的主要作用是数据安全,但它们在流量控制方面也起着重要作用。

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

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

管道化(或简单地说异步)方法提供了最高和最稳定的吞吐量。它可以作为防止代理过载的额外保护措施,因为发布者本身在对代理施加压力之前就处于“流量”(flow)状态。

当您不使用发布者确认时,您将完全依赖 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、流量控制和仲裁队列有了更多的了解。您知道发布者确认对于数据安全很重要,对于发布者和代理之间的流量控制也很重要。但是我们如何实现管道化呢?我们应该使用多大的 in-flight 限制?不同的限制会对吞吐量和延迟产生什么影响?

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

© . This site is unofficial and not affiliated with VMware.