RabbitMQ生产者被流控,消费者速度为何降低

RabbitMQ生产者被流控,消费者速度为何降低

生产环境中的问题

生产环境中,本着尽可能接收客户端发送到服务端的数据的原则,所以不对生产者(生产到RabbitMQ)速度进行控制(实际上也不能去控制生产者速度,因为这会导致更多的消息堆在内存中,从而可能导致进程崩溃)。

当生产者速度过高导致RabbitMQ队列堆积大量消息流控时,RabbitMQ将阻塞生产者连接。主观上消费者速度应该至少保持不变,但实际观察发现,生产者和消费者的速度均受影响,且不平稳。

查阅了相关资料,很少有关于标题提到的现象的资料,最后终于在官方博客中查阅到这个现象,这似乎是一个恶性循环,感觉很无奈:

The problem is that this might not be able to happen. Because your queues are now (albeit briefly) receiving more messages than your consumers can cope with, the queues spend more CPU time dealing with each message than they used to when the queue was empty (the messages now have to be queued up). That takes away CPU time from driving the consumers and unfortunately, as you chose a machine for RabbitMQ which didn’t have a great deal of spare CPU capacity, you start maxing out the CPU. Thus your queues can’t drive your consumers quite as hard as before, which in turn makes the rate of growth of the queues increase. This in turn starts to push the queues to sizes where they have to start pushing messages to disk in order to free up RAM which again takes up more CPU which you don’t have, and by this point, you’re likely in deep trouble.
What can you do?
由于队列现在接收的消息多于消费者可以应付的消息数量,因此与队列为空时(消息现在必须排队等待)相比,队列花费的CPU时间更多地处理每条消息。这会消耗驱动消费者的CPU时间,不幸的是,由于您选择了一台没有大量备用CPU容量的RabbitMQ机器,您开始最大化掏空CPU。因此,你的排队不能像以前一样艰难地推动你的消费者,这反过来又会增加队列的增长速度。这反过来又必须开始推送消息到磁盘,以释放内存,这反过来又占用了你本来就没有多少的CPU。
到此为止,你还能挣扎些什么呢?

好歹官方给出了一个解决办法:那就是不要再继续生产了,赶紧让队列消耗完吧。

At this immediate point, you need to get your queues drained. Because your queues are spending more time dealing with new messages arriving than with pushing messages out to consumers, it’s unlikely that throwing more consumers at the queues is going to significantly help. You really need to get the publishers to stop.

读到官方文档后,突然豁然开朗了。为什么消费速度降低了,因为CPU已经被霸占了。如果是这样的话,应该可以通过queue页面的消费者利用率指标观察到,后续要注意观察一下。

当队列堆积大量的消息时,消费速度不稳定,那就想办法提高消费者利用率吧。
消费者利用率代表了RabbitMQ能够立即deliver message给消费者的能力。
可以通过增加消费者数量,提高Prefetch count,使用批量Ack方式提高消费者利用率。这样消费速度就会比较稳定。

在实际生产环境中,建议针对每个要操作的queue,分别建立生产者线程和消费者线程。
例如,A进程要向Q1、Q2、Q3队列生产消息,那么A进程要启动3个线程分别操作这3个队列,否则如果只使用1个线程1个connection向3个队列生产,当某一个队列流控时connection被阻塞,那么就会影响向其他队列生产的速度。对于消费者也是同样的道理。


顺便了解一下RabbitMQ的流控机制吧。

RabbitMQ的流控机制

官方博客:http://www.rabbitmq.com/blog/2015/10/06/new-credit-flow-settings-on-rabbitmq-3-5-5/

为了防止生产者使用过快的速度生产消息,RabbitMQ实现了一种称为信用流的内部机制,RabbitMQ内部的各种系统将使用该机制来限制生产者,不至于让消费者望尘莫及。

要了解信用流机制,我们需要先了解RabbitMQ的内部如何处理消息发布将消息分页到磁盘。让我们首先看看RabbitMQ中的消息发布是如何工作的。

消息发布

要想了解信用流和其设置是如何影响消息发布的,要先看看RabbitMQ中的内部消息是如何流转的。请记住,RabbitMQ在Erlang中实现,其中进程之间是通过发送消息进行通信的。

当一个RabbitMQ实例运行时,就有数百个Erlang进程交换消息来相互通信。例如,我们有一个reader进程从网络读取AMQP帧。这些帧被转换成AMQP命令,并被转发到AMQP channel进程。如果这个channel进程正在处理一条发布消息,它需要向特定的exchange询问这个消息最终应该发往的queue列表,这意味着该channel将把消息传递给每个queue进程。最后,如果AMQP消息需要持久化,则message store进程将接收它并将其写入磁盘。所以,无论何时我们向RabbitMQ发布AMQP消息,我们都有以下的erlang消息流:

reader process -> channel process -> queue process -> message store

为了防止任何一个进程生产速度过快(导致其下游进程内存爆满,因为这些进程的邮箱可以无限接收消息),我们有一个信用流机制:每个进程最初都会向其上游进程授予一定的信用积分(以发消息的形式)。一旦其上游进程能够处理这些消息中的N个,那么它将授予更多的信用分给其上游进程。

假设我们正在向RabbitMQ发布消息,这意味着reader进程将根据basic.publish收到的每个AMQP向 channel进程发送一条erlang消息 。这些消息中的每一条都会消耗channel进程的一个信用分。一旦channel进程能够处理这些消息中的50个,它将授予reader进程更多的信用分。到现在为止还挺好。

反过来, channel进程将把消息发送到与消息路由规则匹配的queue进程。这将从queue进程授予channel进程的信用分中消耗一个。在queue进程设法处理50次交付之后,它将向channel进程授予50个更多的信用分。

最后,如果一条消息被认为是持久的(它是持久的并且发布到一个持久队列中),它将被发送到message store进程,在这种情况下,也会消耗一个message store进程授予queue进程的信用分。

OK,我们知道消息如何在RabbitMQ内部流动了,并且授予了信用给消息流中的上游流程。棘手的部分来自进程之间授予信用额度。在正常情况下,一个channel进程将处理来自reader进程的50条消息,然后授予reader进程50个信用分,但请记住,channel进程不仅仅是处理发布,它还将消息发送给消费者(当队列没有数据且消费者ready时直接发给消费者),将消息路由到队列以及等等。

如果reader进程以更高的速度将消息发送到channel进程,会发生什么情况?如果我们遇到这种情况,那么channel进程将阻止reader进程,这将导致生产者被RabbitMQ压制。在默认设置下,一旦reader进程发送200条消息到channel进程,但channel进程无法处理至少50条消息以授予reader进程信用分,reader进程将被阻塞。

再次,在正常情况下,一旦channel进程处理了积压消息,它将给予reader进程更多的信用分,但是这是一个问题。由于类似的原因,如果channel进程queue进程阻塞呢?那么本来应该推向reader进程的信用分将被推迟,reader进程将继续受阻。

一旦queue进程处理了积压消息,它将为channel进程授予更多的信用分,解除封锁,这将导致channel进程reader进程授予更多的信用分,解除封锁。再次,这是在正常情况下,但是,你猜对了,如果message store进程阻塞了queue进程呢?然后,对channel进程的信用将被延期,这将被阻止,推迟给reader进程的信用,使reader进程被阻止。

reader process <–[grant]– channel process <–[grant]– queue process <–[grant]– message store

拥有一个channel和一个queue process使得事情变得更容易,但它可能不能反映现实。RabbitMQ用户在同一连接上有多个channel 发布消息是很常见的。更常见的是将一条消息路由到多个队列。我们刚刚解释过的信用流机制会发生什么情况:如果其中一个queue阻塞了channel,那么reader也会被阻止。

问题是,从reader的角度来看,当我们从网络上读取一帧信息时,我们甚至不知道它属于哪个channel。请记住,channel是AMQP连接之上的逻辑概念。所以即使一个新的AMQP命令最终会在一个没有阻塞reader的channel中,reader也无法知道它。所以建议one channel one connection

请注意,我们只阻止发布连接,消费者连接不受影响,因为我们希望消费者能继续从队列中消费消息。这是专为发布消息而建立连接可能更好的一个很好的理由,以及专用于消费者的连接。即publish connection and consume connection

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值