.net RabbitMQ之CreateBasicProperties:Lazy Queue,Persistent Message

本文介绍如何在RabbitMQ中实现消息持久化,确保服务重启后消息不丢失,以及LazyQueue的使用方法,减少内存消耗,适合硬件内存不足的情况。

注:以下程序使用C#实现

我们在使用RabbitMQ发送消息到队列之后,在某些场景下,我们希望当消息没有被消费时,如果系统发生故障停止或者我们手动重启服务之后,消息仍然存在队列之中,下面我们看一下普通的使用场景:

var channel=connection.CreateModel();
channel.QueueDeclare("mytest",false,false,false,null);
channel.QueueBind("mytest",string.Empty,"",null);
for(var i=0;i<10;i++){
channel.BasicPublish(string.Empty,"mytest",null,Encoding.UTF8.GetBytes("你好"));
}

上面代码是我们往mytest队列中塞了10条数据进去,这10条消息并没有被消费,但是当我们重启RabbitMQ之后,会发现,mytest队列中的消息变为NaN了,代表消息已经丢失了,在某些时候,由于重启服务而丢失消息,是我们不希望看到的

解决办法:

在程序中使用CreateBasicProperties方法,通过该方法创建一个属性实例,给实例的Persistent字段赋值为true即可

var channel=connection.CreateModel();
channel.QueueDeclare("mytest",false,false,false,null);
channel.QueueBind("mytest",string.Empty,"",null);
var properties=channel.CreateBasicProperties();
properties.Persistent=true;
for(var i=0;i<10;i++){
channel.BasicPublish(string.Empty,"mytest",properties,Encoding.UTF8.GetBytes("你好"));
}

下面介绍一下RabbitMQ中的Lazy Queue用法。注:LazyQueue就是将消息写入磁盘,只有用到的时候,才会写入内存

如果我们采用Lazy Queue+InMemory也就是配置了LazyQueue并且properties.Persistent=false;

就是消息不写入内存,写入磁盘

var channel=connection.CreateModel();
//使用方法
channel.QueueDeclare("mytest",false,false,false,new Dictionary<string,object>{
{"x-queue-mode","lazy"}//设置为懒队列
});
channel.QueueBind("mytest",string.Empty,"",null);

for(var i=0;i<100000;i++){
//注意!以下的msg数据非常大,每个msg高达百兆
var msg=string.Format("{0}测试",string.join(",",Enumrable.Range(1,100000)));
channel.BasicPublish(string.Empty,"mytest",null,Encoding.UTF8.GetBytes(msg));
}

这样发送消息,我们会发现,内存的大小几乎没有改变,这说明Lazy确实不会将消息写入内存,而是临时存储在磁盘上,但是这种写法会有问题,就是重启服务之后,消息会丢失。

为了不丢失消息,我们需要采用这种写法 Lazy Queue+Disk

var channel=connection.CreateModel();
//添加下面两句
var properties=channel.BasicCreateProperties();
properties.Persistent=true;
//使用方法
channel.QueueDeclare("mytest",false,false,false,new Dictionary<string,object>{
{"x-queue-mode","lazy"}//设置为懒队列
});
channel.QueueBind("mytest",string.Empty,"",null);

for(var i=0;i<100000;i++){
//注意!以下的msg数据非常大,每个msg高达百兆
var msg=string.Format("{0}测试",string.join(",",Enumrable.Range(1,100000)));
channel.BasicPublish(string.Empty,"mytest",null,Encoding.UTF8.GetBytes(msg));
}

这样,内存既不会暴增,也能将消息持久化

如果是Default Queue+InMemory方法

var channel=connection.CreateModel();

//使用方法
channel.QueueDeclare("mytest",false,false,false,null);
channel.QueueBind("mytest",string.Empty,"",null);

for(var i=0;i<100000;i++){
//注意!以下的msg数据非常大,每个msg高达百兆
var msg=string.Format("{0}测试",string.join(",",Enumrable.Range(1,100000)));
channel.BasicPublish(string.Empty,"mytest",null,Encoding.UTF8.GetBytes(msg));
}

这种方式内存会暴增,并且消息不会持久化到磁盘

我们可以采用Default Queue+Disk方式,将消息持久化到磁盘

var channel=connection.CreateModel();
//添加下面两句
var properties=channel.BasicCreateProperties();
properties.Persistent=true;
//使用方法
channel.QueueDeclare("mytest",false,false,false,null);
channel.QueueBind("mytest",string.Empty,"",null);

for(var i=0;i<100000;i++){
//注意!以下的msg数据非常大,每个msg高达百兆
var msg=string.Format("{0}测试",string.join(",",Enumrable.Range(1,100000)));
channel.BasicPublish(string.Empty,"mytest",null,Encoding.UTF8.GetBytes(msg));
}

但是需要注意的是,这种方式,内存也会暴增

综上所述,如果我们的消息不需要相应非常快,并且硬件内存不足时,可以采用Lazy Queue+Disk方式,如果时其它情况,则需另行讨论

<think>嗯,用户这次想了解LazyQueue的概念,看来是对RabbitMQ的高级特性感兴趣。之前我们讨论过消息持久化的问题,用户可能是在优化消息队列时遇到了性能或可靠性方面的考量。 从引用内容看,用户已经接触过LazyQueue的代码示例(SpringBoot注解声明方式),但需要更系统的概念解释。值得注意三个技术细节:1)Lazy模式的核心设计目标是解决海量消息堆积时的内存压力;2)它的磁盘存储机制与传统持久化有本质区别;3)在Spring中声明队列时既要设置durable又要加lazy参数。 用户提供的代码片段很有参考价值,特别是引用[2]和[3]展示了两种等价的LazyQueue声明方式(QueueBuilder和@RabbitListener)。不过用户可能没意识到,测试用例[4]中故意使用NON_PERSISTENT发送到LazyQueue是个矛盾操作——虽然LazyQueue本身能存盘,但非持久化消息在重启后仍会丢失,这点需要特别说明。 在解释原理时,应该对比普通队列的内存存储机制。比如普通队列收到1万条消息时,RabbitMQ会尝试将部分消息换页到磁盘,但这个过程是阻塞式的;而LazyQueue从第一字节就直写磁盘,吞吐量反而更高。不过用户可能更关心实际影响:当消息量小于10万条时,LazyQueue的延迟会比普通队列高2-3倍。 用户身份推测是Java后端开发,正在设计消息中间件层。ta的提问从持久化转到LazyQueue,说明可能在处理大数据量场景。需要提醒几个实用注意点:1)LazyQueue不兼容TTL过期删除(需改队列策略);2)管理后台监控时"内存消息数"指标将恒为0;3)集群环境下镜像队列的同步逻辑有变化。</think>### LazyQueue 概念详解 LazyQueueRabbitMQ 3.6.0 引入的一种特殊队列模式,其核心设计理念是**将消息优先存储到磁盘而非内存**,从而显著优化队列处理海量消息时的资源占用和稳定性。 #### 核心特性 1. **消息存储机制** - **普通队列**:消息默认同时存入内存和磁盘(若开启持久化),内存不足时触发**换页(paging)**,将部分消息写入磁盘(阻塞式操作,影响性能)[^1][^2]。 - **LazyQueue**:消息**直接写入磁盘**,仅在消费时加载到内存,避免内存压力。即使消息未显式声明持久化(`deliveryMode=1`),也会被存储到磁盘[^1][^3]。 2. **性能优势场景** | 场景 | 普通队列 | LazyQueue | |---------------------|----------------------------|---------------------------| | 海量消息堆积 | 内存易耗尽,触发阻塞式换页 | 内存占用稳定,无阻塞风险 | | 突发流量 | 可能因内存不足拒绝消息 | 稳定接收消息 | | 消费速率低于生产速率 | 内存压力持续增大 | 磁盘缓冲,内存无压力 | 3. **可靠性增强** - 消息默认写入磁盘(即使未设置 `deliveryMode=2`),重启后消息不丢失(与普通非持久化消息不同)[^3]。 - 但需注意:**显式设置 `deliveryMode=2`(持久化)仍是生产环境的最佳实践**,确保元数据同步持久化。 #### 代码配置示例 ##### Spring Boot 声明方式 ```java // 方式1: QueueBuilder 构建 @Bean public Queue lazyQueue() { return QueueBuilder .durable("lazy.queue") // 持久化队列 .lazy() // 关键:启用Lazy模式 .build(); } // 方式2: 注解声明 @RabbitListener(queuesToDeclare = @Queue( name = "lazy.queue", durable = "true", // 持久化队列 arguments = @Argument( name = "x-queue-mode", value = "lazy" // 关键:启用Lazy模式 ) )) public void listenLazyQueue(String msg) { // 消费逻辑 } ``` #### 适用场景 1. **高吞吐量低消费速率** 如日志收集、离线分析等场景,生产速度远高于消费速度。 2. **内存敏感型系统** 避免因消息堆积导致内存溢出(如嵌入式设备或容器环境)。 3. **需高可用但无需低延迟** 接受磁盘IO带来的毫秒级延迟,换取系统稳定性。 #### 注意事项 1. **性能权衡** 磁盘IO导致单条消息的**处理延迟增加**(约0.1~10ms),不适合超低延迟场景。 2. **监控差异** 管理界面中内存消息数(`messages_ram`)始终为0,需关注磁盘消息数(`messages_persistent`)[^2]。 3. **TTL限制** 若队列设置消息TTL(过期时间),过期消息可能不会立即清理(依赖消费者触发)。 > **关键结论**:LazyQueue 通过牺牲少量延迟,从根本上解决了消息堆积导致的内存瓶颈问题,是处理海量数据的“安全气囊”。重启后消息保留(因消息存盘),但需配合 `durable=true` 确保队列元数据不丢失[^1][^2][^3]。 --- ### 相关问题 1. **LazyQueue 与传统持久化队列的性能对比指标有哪些?** 2. **如何监控 LazyQueue 的磁盘使用情况以避免存储空间耗尽?** 3. **在 LazyQueue 中,消息消费时从磁盘加载到内存的具体过程是怎样的?** 4. **LazyQueue 是否适合用作 RabbitMQ 的死信队列(DLQ)?为什么?** 5. **在集群环境下,LazyQueue 的镜像队列同步机制有何特殊之处?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值