RabbitMQ 队列分为几种类型,按照不同维度来分,可以分为排他性队列、普通队列、延迟队列、惰性队列、发布订阅队列等。
今天我们讨论的主角是惰性队列 Lazy Queue。众所周知,队列可以存储消息并实现消息收发,这应该是消息队列中最重要的功能之一。
我们使用消息队列有几个优势,解耦、高效、发完不管、高可用。上一篇我们聊了RabbitMQ的镜像队列机制,镜像队列是高可用实现的一个有利保障,但在高可用的同时,必须提供高效的服务,才能被更多普通劳苦大众所接受。
RabbitMQ提供高效服务的几种途径,设置RAM节点、自动同步,先内存后磁盘的读取方式。在队列设置持久化后,我们读取队列中的消息,如果都是先从内存后从磁盘读取,那么无形会占用更多系统资源,毕竟内存应该留给更多有需要的地方。
如果发送端过快或消费端宕机,导致消息大量积压,此时消息还是在内存和磁盘各存储一份,在消息大爆发的时候,MQ服务器会撑不住,影响其他队列的消息收发,能不能有效的处理这种情况呢。答案 惰性队列。
RabbitMQ从3.6.0版本开始引入了惰性队列(Lazy Queue)的概念。惰性队列会尽可能的将消息存入磁盘中,而在消费者消费到相应的消息时才会被加载到内存中,它的一个重要的设计目标是能够支持更长的队列,即支持更多的消息存储。当消费者由于各种各样的原因(比如消费者下线、宕机亦或者是由于维护而关闭等)而致使长时间内不能消费消息造成堆积时,惰性队列就很有必要了
默认情况下,当生产者将消息发送到RabbitMQ的时候,队列中的消息会尽可能的存储在内存之中,这样可以更加快速的将消息发送给消费者。即使是持久化的消息,在被写入磁盘的同时也会在内存中驻留一份备份。当RabbitMQ需要释放内存的时候,会将内存中的消息换页至磁盘中,这个操作会耗费较长的时间,也会阻塞队列的操作,进而无法接收新的消息。虽然RabbitMQ的开发者们一直在升级相关的算法,但是效果始终不太理想,尤其是在消息量特别大的时候
惰性队列会将接收到的消息直接存入文件系统中,而不管是持久化的或者是非持久化的,这样可以减少了内存的消耗,但是会增加I/O的使用,如果消息是持久化的,那么这样的I/O操作不可避免,惰性队列和持久化消息可谓是“最佳拍档”。注意如果惰性队列中存储的是非持久化的消息,内存的使用率会一直很稳定,但是重启之后消息一样会丢失
队列具备两种模式:default和lazy。默认的为default模式,在3.6.0之前的版本无需做任何变更。lazy模式即为惰性队列的模式,可以通过调用channel.queueDeclare方法的时候在参数中设置,也可以通过Policy的方式设置,如果一个队列同时使用这两种方式设置的话,那么Policy的方式具备更高的优先级。如果要通过声明的方式改变已有队列的模式的话,那么只能先删除队列,然后再重新声明一个新的
设置队列为惰性队列的命令:
rabbitmqctl set_policy Lazy "^myqueue$" '{"queue-mode":"lazy"}' --apply-to queues
惰性队列和普通队列相比,只有很小的内存开销。这里很难对每种情况给出一个具体的数值,但是我们可以类比一下:当发送1千万条消息,每条消息的大小为1KB,并且此时没有任何的消费者,那么普通队列会消耗1.2GB的内存,而惰性队列只消耗1.5MB的内存
据官网测试数据显示,对于普通队列,如果要发送1千万条消息,需要耗费801秒,平均发送速度约为13000条/秒。如果使用惰性队列,那么发送同样多的消息时,耗时是421秒,平均发送速度约为24000条/秒。出现性能偏差的原因是普通队列会由于内存不足而不得不将消息换页至磁盘。如果有消费者消费时,惰性队列会耗费将近40MB的空间来发送消息,对于一个消费者的情况,平均的消费速度约为14000条/秒。
如果要将普通队列转变为惰性队列,那么我们需要忍受同样的性能损耗。当转变为惰性队列的时候,首先需要将缓存中的消息换页至磁盘中,然后才能接收新的消息。反之,当将一个惰性队列转变为普通队列的时候,和恢复一个队列执行同样的操作,会将磁盘中的消息批量的导入到内存中。
队列参数的设置:
-
Message TTL(x-message-ttl):设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒, 类似于redis中的ttl,生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除, 特性Features=TTL, 单独为某条消息设置过期时间AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”);
-
Auto Expire(x-expires): 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp
-
Max Length(x-max-length): 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=Lim
-
Max Length Bytes(x-max-length-bytes): 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B
-
Dead letter exchange(x-dead-letter-exchange): 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX
-
Dead letter routing key(x-dead-letter-routing-key):将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK
-
Maximum priority(x-max-priority):优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费,
-
Lazy mode(x-queue-mode=lazy): Lazy Queues: 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中
-
Master locator(x-queue-master-locator)
总结:
惰性队列的存在是为了减少内存占用,避免由于内存不足而产生的换页操作。
但对比惰性队列和普通队列,如果在内存和磁盘均不设限制的话,采用普通队列的效率会更高,毕竟磁盘再快,对比内存也会差一个数量级。
但如果服务器的配置捉襟见肘的话,可以考虑惰性队列的存在,毕竟惰性队列相比普通队列,性能差异并不大,在极端情况下还会更好,您觉得呢?
使用lazy queue会有以下几种搭配
lazy queue 消息不持久化 , 但是这种模式还是会把消息放到硬盘里,RAM的使用率会一直很稳定,但是重启后一样会丢失消息
lazy queue 消息持久化,这种方式无疑是最佳搭配,消息放到硬盘并且不会因为服务器重启而丢失,面对高并发也是从容不已。 面包和咖啡更配哦