赞
踩
本文是rabbitmq 官方社区的一篇关于一些队列的基本理论的文章,觉得写得挺不错,主要是对rabbitmq的QoS进行了深入的分析。尝试着翻译一下~原文链接
首先,考虑一下,我们在Rabbit上新建了一个队列,并且有几个消费者此队列,如果我们没有设置QoS(basic.qos),那么Rabbit 会尽可能快的把队列的消息推送到客户端(博主备注:尽可能快是指Rabbit会一直发送消息直到把socket 缓冲区打满)。此时消费者客户端内存会迅速膨胀因为它们缓存了大量的消息在内存中。这种情况下,队列消息可能已经是空的,但是可能有数百万的未确认消息存储在消费者客户端,等待消费者处理,此时,如果在增加新的消费者,那么此消费者将无消息可消费。此时,消息存储在已经存在的客户端,或许会存在很长时间(博主备注:此时消息已经被Rabbit 服务端分发给客户端,并被客户端缓存起来了),如果增加消费者以求更快的处理消息其实是达不到效果的。
所以,默认的QoS的prefetch 使得客户端可以无限制的缓存(博主备注:其实还是要考虑带宽,网卡被打满时),这时明显会导致处理效率低下。那么我们设置Qos frefetch的主要目的是什么呢?答案是保持消费者始终处于忙碌状态,并且通过最大限度的减少客户端缓冲区大小,把更多的消息留在Rabbit队列中,可以供新的消费者使用或者说在消费者空闲时发送给消费者。
举个栗子:假如Rabbit 从队列里取出一条消息通过网络发送到消费端需要花费50ms,而消费者处理一条消息需要花费4ms。当消费者处理完成后会发送一条ack消息到Rabbit服务端,同样也花费50ms.这里可以看到,一条完整的消息的处理需要花费104ms,如果我们每次只预取一条(prefetch = 1),那么在一次消息处理中客户端忙的时间只有4ms,其效率只有4/104 = 3.8%,正常情况下我们希望客户端一直处于忙碌状态。
所以这里做一个除法,一次完整的消息处理周期时间除以客户端处理的时间 ,即104 / 4 = 26。那么如果我们设置预取 26条就可以解决我们的问题:假设客户端每次缓存26条数据,每条消息处理需要花费4ms,那么当客户端处理完第一条消息,花费第一个4ms后,客户端发送一个ack并继续处理缓存中的下一条消息,ack消息花费50ms到达Rabbit 服务端,服务端发送新的消息到客户端,同样花费50ms到达,那么当消费者处理完成上次取得26条消息之后,新的消息已经到达并等待消费者处理。这样,消费者能始终保持忙碌状态(博主备注:SpringAMQP 默认的prefetch 从1改成250以提高吞吐量),此时增加prefetch 并不会获得更快的处理速度,反而会导致消息的处理延时:客户端缓存消息的时间不超过其处理的时间,客户端始终处于饱和状态,实际上,客户端能够在下一条消息到达之前完全耗尽缓冲区,因此缓冲区实际上保持为空。
现在假设网络和处理时间保持一致,但是考虑一下,当网络突然发生延迟:预取的缓冲区就不够大了,此时消费者处于空闲状态,等待新的消息到来,其处理速度是大于Rabbit发送的速度。
为了解决这个问题,我们可以设置双倍或者近似双倍的预取数量,比如从26设置到51,消息处理保持在4ms每条,此时缓存了204ms(处理时间)的消息在缓冲区,这样也能够保证消费者处于忙碌状态。
但是假如网络恢复正常,双倍的预取消息会导致消息在客户端保存一段时间而不是立即处理。还是以上述处理时间举例,一次取51条消息,但是100ms内,消费者能处理25条信息,那么新的消息到达时缓冲区还有25条消息,每条消息都会在缓冲区保存100ms,无疑增加了消息处理的延迟。
因此,我们看到增加预取消息数量能够应对较差的网络性能,保持客户端忙碌,但是也大大增加网络正常运行时的延迟。
同样的,假如网络性能良好,但是客户端处理消息需要花费40ms而不是4ms,如果之前Rabbit队列保存稳定的长度(即生产和消费速率保持一致),那么队列将快速增长,因为消费速度下降为原来的十分之一。这种情况下我们可以考虑新增消费者处理,但是假设客户端缓冲区大小为26条消息,ack和Rabbit发送消息都需要50ms,那么在100ms内,客户端只能处理100 / 40 = 2.5条消息,此时缓冲区长度为22条消息。新的消息不会立即处理,而是位于缓冲区的第23位,此时延迟位22*40=880ms,Rabbit的网络延迟为50ms,那么额外的880ms延迟现在为 (880/(880+50) =0.946)95%.
结语:文章最后提到了CoDel 算法来解决这个问题,有兴趣的同学可以搜索一下,后续的就不在翻译了~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。