rabbitmq 持久化_SpringBootRabbitMQ消息处理持久化问题

本文探讨了SpringBoot中RabbitMQ消息处理的持久化问题,旨在防止消息丢失。通过设置`autoDelete`为false实现队列持久化,确保即使服务重启,消息仍可保留。同时,提到了在消费者异常时可能导致的死循环问题,并推荐使用try/catch、死信、重定向和人工干预的策略。最后,讨论了手动ACK和Confirm确认确保100%消息投递成功的方法,以及幂等性设计来避免分布式定时任务引发的消息重发问题。
摘要由CSDN通过智能技术生成

08:SpringBoot-RabbitMQ消息处理-持久化问题

SpringBoot-RabbitMQ消息处理-持久化问题

目标:解决开发中关于消息丢失和持久化的问题。

步骤1:修改autoDelete的状态即可,false是持久化,true是非持久化 

bd2edd9e8e3d5ab3d6c65d21f16360f8.png

概述:消息丢了怎么办?一般我们进行持久化即可,操作如下: 

packagecom.itbooking.springbootrabbitmqdurabledirectconsumber.mq;import org.springframework.amqp.core.ExchangeTypes;import org.springframework.amqp.rabbit.annotation.*;import org.springframework.stereotype.Component;// autoDelete 是否持久化操作。false是持久化,true不持久化,@Component@RabbitListener(bindings =@QueueBinding(value = @Queue(value ="${mq.config.queue.error}",autoDelete = "false"),exchange = @Exchange(value ="${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.error.routing.key}"))public class ErrorReceiver {@RabbitHandlerpublic void process(String msg) {System.out.println("error----------->接受到的的消息是:" + msg);}}

把上面的autoDelete 改成false,即持久化操作。这样可以防止因为网络故障问题导致消费消息不连续的问题。

注意测试的时候后一定是要:先在rabbitmq管控台先删除,在重新生成在测试,结论是 :

1、这个时候把autoDelete改成true以后,一定关闭连接,队列就直接丢失。

2、如果`autoDelete`是false,就是持久化的队列,这个时候即使你重启了rabbitmq服务,消息依然存在。 

6d261d3b7da2d18dd0453f2441b9b239.png

测试代码如下:

生产者

springboot-rabbitmq-durable-direct-producer

1:引入依赖

2:配置rabbitmq 

server.port=8097spring.application.name=springboot-rabbitmq-durabledirect-producer# rabbitmqspring.rabbitmq.host=192.168.153.173spring.rabbitmq.port=5672spring.rabbitmq.username=adminspring.rabbitmq.password=admin# 设置交换机mq.config.exchange=log.direct

3: 发送消息

packagecom.itbooking.springbootrabbitmqdurabledirectproducer.mq;import org.springframework.amqp.core.AmqpTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import java.util.Date;@Componentpublic class Sender {@Autowiredprivate AmqpTemplate amqpTemplate;@Value("${mq.config.exchange}")private String exchangeName;public void sendMessage(int i) throwsInterruptedException {String msg = i+":Hello rabbitmq " + newDate();amqpTemplate.convertAndSend(exchangeName,"log.error.routing.key",msg);System.out.println(msg);}}

消费者

springboot-rabbitmq-durable-direct-consumber

1:配置rabbitmq 

server.port=8196spring.application.name=springboot-rabbitmq-durabledirect-consumber# rabbitmqspring.rabbitmq.host=192.168.153.173spring.rabbitmq.port=5672spring.rabbitmq.username=adminspring.rabbitmq.password=admin# 设置交换机mq.config.exchange=log.direct# 设置路由键mq.config.queue.info=log.infomq.config.queue.info.routing.key=log.info.routing.keymq.config.queue.error=log.errormq.config.queue.error.routing.key=log.error.routing.key

2:定义消费者类 

packagecom.itbooking.springbootrabbitmqdurabledirectconsumber.mq;import org.springframework.amqp.core.ExchangeTypes;import org.springframework.amqp.rabbit.annotation.*;import org.springframework.aop.ThrowsAdvice;import org.springframework.stereotype.Component;// autoDelete 是否持久化操作。false是持久化,true不持久化,@Component@RabbitListener(bindings =@QueueBinding(value = @Queue(value ="${mq.config.queue.error}",autoDelete = "false"),exchange = @Exchange(value ="${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.queue.error.routing.key}"))public class ErrorReceiver {@RabbitHandlerpublic void process(String msg) {System.out.println("error----------->接受到的的消息是:" + msg);}}

3:启动消费者等待消息消费 

测试类 

packagecom.itbooking.springbootrabbitmqdurabledirectproducer;importcom.itbooking.springbootrabbitmqdurabledirectproducer.mq.Sender;import org.junit.Test;import org.junit.runner.RunWith;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTestpublic classSpringbootRabbitmqDurableDirectProducerApplicationTests {@Autowiredprivate Sender sender;@Testpublic void contextLoads() throwsInterruptedException {for (int i = 0; i <200 ; i++) {Thread.sleep(2000);sender.sendMessage(i);}}}
  • 给发送消息休眠200秒,慢慢发送。

  • 这个时候停止消费者接受消息,继续发送消息给消费队列。测试持久化的作用。

  • 如果autodelete是true是临时队列,关闭消费者即可删除队列,就会造成消息丢失,测试的时候务必先删除队列让其重新创建,否则无效。

3ab8d66bd22dc9ac317c9ed76eb4f8f6.png

接受消息关闭之前: 

2cacedcdb8a972ef604e58616b1ed273.png

关闭之后在启动: 

336443966aca22ce29889229d6f37e77.png

丢失了消息!!!!

  • 如果设置为false,它继续接着没有消费的消息继续消费。 

06a9a825230c4a3a98c28052e3b1f0b1.png

关闭之前消息消费到7的位置,重启以后如下:

836f752d96c83bf49afee6d114520437.png

消费出现异常:因为rabbitmq当消费者出现异常的时候,rabbitmq的默认情况是一种:自动确认过程,而报异常以后这自动确认的过程,就没办法确认,没办法确认,它就会把这个消息重新放入到消息队列继续去发送,这样就造成死循环。

  • 开启消息重试即可,如果你单机版,会一直重试自己服务器,如果集群版:切换别的服务器重试。如果重试的次数已经达到限制次数,还是都失败,这个消息直接扔掉。

  • 推荐做法:try/catch + 死信 + 重定向+1重试 + 人工干预短信预警消费出现服务宕机:用持久队列 

10:消息如何保障100%的投递成功(重点)-手动ACK和Confifirm确认


消息落库,对消息状态进行打标。做法就是把消息发送MQ一份,同时存储到数据库一份。然后进行消息装填的控制,发送成功1,发送失败0。必须结合应答来完成。对于哪些如果没有发送成功的消息,可以采用定时器进行轮询发送。

d648d98c5334c69e06fee2bfa16fa0f4.png

存在问题:可能出现分布式定时任务出现消息重发的情况。因为在消息保存进去的时候状态是0,其实这个时候ACK是可以回执,但是定时器恰好在那个时间点运行到了。就出现了消息重发的问题,解决方案:幂等性

生产者1:

配置依赖  

<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-amqpartifactId>dependency>

2:配置applicaiton.properties

#服务器server.port=7878#配置rabbitmqspring.rabbitmq.port=5672spring.rabbitmq.host=192.168.153.176spring.rabbitmq.virtual-host=/spring.rabbitmq.username=adminspring.rabbitmq.password=admin# 定义交换机mq.config.exchange=log.ack.direct# 设置路由keymq.config.routekey=red.ack.routing.key# 开启消息发送确认机制,重要!!!#spring.rabbitmq.publisher-confirm-type=correlatedspring.rabbitmq.publisher-confirms=true

3:生产者 

package com.icoding.springbootrabbitfanoutproducer.mq;import org.springframework.amqp.core.AmqpTemplate;importorg.springframework.amqp.rabbit.connection.CorrelationData;importorg.springframework.amqp.rabbit.core.RabbitTemplate;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;@Servicepublic class Sender {// 初始化一个rabbitmq的实例对象@Autowiredprivate RabbitTemplate rabbitTemplate;@Value("${mq.config.exchange}")private String exchangeName;@Value("${mq.config.routekey}")private String routekey;@PostConstructpublic void setup() {// 消息发送完毕后,则回调此方法,ack代表发送是否成功rabbitTemplate.setConfirmCallback(newRabbitTemplate.ConfirmCallback() {@Overridepublic void confirm(CorrelationDatacorrelationData, boolean ack, String cause) {// 如果ack为true,代表MQ已经准备收到消息。if (!ack) {return;}try {System.out.println("接受确认的数据是:" + correlationData.getId());System.out.println("本地消息保存成功!!!");} catch (Exception ex) {System.out.println("警告:修改本地消息表的状态时出现异常!" + ex.getMessage());}}});}public void sendMessage(int i) throwsInterruptedException {//下单String msg = "{id:10001,price:100}";//amqpTemplate.convertAndSend(exchangeName,"sms.routing.key","direct--订单保存成功,去发送短信和微信.....");rabbitTemplate.convertAndSend(exchangeName,routekey, msg, new CorrelationData("10001"));System.out.println(msg);}}

消费者

1:配置手动确认 applicaiton.properties 

#服务器server.port=7879#配置rabbitmqspring.rabbitmq.port=5672spring.rabbitmq.host=192.168.153.176spring.rabbitmq.virtual-host=/spring.rabbitmq.username=adminspring.rabbitmq.password=admin# 定义交换机mq.config.exchange=log.ack.direct# 定义队列三个消费者队列# 红包队列mq.config.queue.red=order.ack.red.log# 设置路由keymq.config.red.routekey=red.ack.routing.key# 解决消息的ACK死循环问题--自动确认spring.rabbitmq.listener.simple.retry.enabled=true# 最多重试几次,但是在实际的生产中如果不重要的可以这样做,spring.rabbitmq.listener.simple.retry.max-attempts=5# 重要,开启手动ack,控制消息在MQ中的删除,重发。spring.rabbitmq.listener.simple.acknowledgemode=MANUAL

2:定义消费者并且手动确认

packagecom.icoding.springbootrabbitfanoutconsumber.mq;import com.rabbitmq.client.Channel;import org.springframework.amqp.core.ExchangeTypes;import org.springframework.amqp.rabbit.annotation.*;importorg.springframework.amqp.rabbit.core.RabbitTemplate;import org.springframework.amqp.support.AmqpHeaders;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.messaging.handler.annotation.Header;import org.springframework.stereotype.Service;@Service// 用RabbitListener来完成队列和交换机的定义和绑定关系以及设置交换机的类型// autoDelete= true代表非持久化 false 持久化@RabbitListener(bindings = @QueueBinding(value =@Queue(value="${mq.config.queue.red}",autoDelete="false"),exchange =@Exchange(value ="${mq.config.exchange}",type = ExchangeTypes.DIRECT),key = "${mq.config.red.routekey}"))public class RedxinReceiver {@AutowiredRabbitTemplate rabbitTemplate;@RabbitHandlerpublic void getMessage(String message, Channelchannel, @Header(AmqpHeaders.DELIVERY_TAG) long tag){try {System.out.println("red------接收到订单的消息了,内容是:" + message);//参数1:deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用basic.deliver 方法向消费者推送消息,这个方法携带了一个delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel//参数2:multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息channel.basicAck(tag,false);}catch (Exception ex) {ex.printStackTrace();}}}

测试类

SpringbootRabbitDirectProducerAckApplicationTests.java 

package com.icoding.springbootrabbitfanoutproducer;importcom.icoding.springbootrabbitfanoutproducer.mq.Sender;import org.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;@SpringBootTestclassSpringbootRabbitDirectProducerAckApplicationTests {@Autowiredprivate Sender sender;@Testvoid contextLoads() throws Exception{sender.sendMessage(1);}}

运行结果:消费者消费消息 

red------接收到订单的消息了,内容是:{id:10001,price:100}

生产者回执消息如下: 

{id:10001,price:100}接受确认的数据是:10001本地消息保存成功!!!

下节课预告:SpringBoot-整合RabbitMQ常见问题以及总结

85f2792ab3c54fa9660d25fca28760db.png

记得点个在看,或者转发朋友圈哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值