谷粒商城高级篇—RabbitMQ
延时队列(实现定时任务)
未付款订单,超时自动取消并释放占有。
常用解决方案:定时任务轮询
缺点:消耗内存,增加数据库压力,时间误差大
解决:RabbitMQ 消息TTL和死信Exchange结合
-
消息TTL:消息存活时间,RabbitMQ可以对队列和消息分别设置TTL,同时设置取小的。
-
下列条件,消息会进入死信路由
- 消息被Consumer拒收,并reject方法的参数requeue是false。
- 消息TTL到了,消息过期
- 队列的长度限制满了,前面的消息会被丢弃或扔到死信路由
-
Dead letter Exchange其实是一种普通的exchange,和创建其他exchange没有两样,只是
控制消息在一段时间变成死信,控制死信的消息被路由到某个指定交换机,实验延时队列。
延时队列实现-1
延时队列实现-2
推荐方式一给队列设置过期时间,因为RabbitMQ是惰性检查。
SpringBoot整合RabbitMQ
-
引入依赖
<!--RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> -
自动配置了 RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate
#配置RabbitMQ spring.rabbitmq.host=127.0.0.1 spring.rabbitmq.port=5672 spring.rabbitmq.virtual-host=/ 测试 amqpAdmin、rabbitTemplate
@Autowired AmqpAdmin amqpAdmin; @Autowired RabbitTemplate rabbitTemplate; /* * * 如何创建Exchange、Queue、Binding * 如何收发消息 */ @Test public void createExchange() { //Exchange amqpAdmin.declareExchange(new DirectExchange("hello-java-exchange",true,false)); log.info("Exchange[{}]创建成功","hello-java-exchange"); } @Test public void createQueue(){ amqpAdmin.declareQueue(new Queue("hello-java-queue",true,false,false)); log.info("Queue[{}]创建成功","hello-java-queue"); } @Test public void createBinding(){ amqpAdmin.declareBinding(new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null)); log.info("Binding[{}]创建成功","hello-java-binding"); } @Test public void sendMessageTest(){ OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity(); reasonEntity.setId(1L); reasonEntity.setCreateTime(new Date()); reasonEntity.setName("哈哈"); // 如果发送消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable String msg = "hello world!"; rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",reasonEntity); log.info("消息发送完成{}",reasonEntity); } -
@EnableRabbit
-
监听消息 @RabbitListener
@RabbitListener(queues = {"hello-java-queue"}) public void recieverMessage(Message message, OrderReturnReasonEntity content, Channel channel){ //消息体 byte[] body = message.getBody(); //消息头 MessageProperties messageProperties = message.getMessageProperties(); System.out.println("接收到消息。。。内容:"+message+"==>类型:"+message.getClass()); } - @RabbitListener 类+方法上
- @RabbitHandler 标在方法上(重载区分不同的消息)
Queue可以很多人监听,同一个消息只能一个客户端收到
只要一个消息完全处理完,方法运行结束,可以接收下一个消息
RabbitMQ消息确认机制-可靠抵达
保证可靠抵达,可以使用事务消息,性能下降250倍,为此引入确认机制。
-
publisher confirmCallBack 确认模式
#开启发送端确认 #spring.rabbitmq.publisher-confirms=true 已被弃用 # NONE CORRELATED SIMPLE spring.rabbitmq.publisher-confirm-type=correlated //定制RabbitTemplate @PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法 public void initRabbitTemplate(){ //设置确认回调 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){ /* * * @param correlationData 当前消息的唯一关联数据 * @param ack 消息是否成功收到 * @param cause 失败的原因 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("confirm...correlationData["+correlationData+"]==>ack["+ack+"]==>cause["+cause+"]"); } }); } -
publischer returnCallBack 未投递到 queue退回模式
#开启发送端消息抵达确认 spring.rabbitmq.publisher-returns=true #只要抵达队列,以异步方式优先回调我们这个returnconfirm spring.rabbitmq.template.mandatory=true //rabbitTemplate.setReturnsCallback();已被弃用 rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() { @Override public void returnedMessage(ReturnedMessage returnedMessage) { System.out.println(returnedMessage); } }); -
consumer ack机制(保证每个消息被正确消费,此时broker删除这个消息)
1、默认自动确认,只要消息接收到,客户端自动确认,服务端就会移除这个消息
问题:收到多个消息,自动回复ack,只有一个消息处理成功,宕机,消息丢失
解决:消费者手动确认
#手动ack消息 spring.rabbitmq.listener.simple.acknowledge-mode=manual 2、如何签收?
@RabbitHandler public void recieverMessage(Message message, OrderReturnReasonEntity content, Channel channel){ //...消息处理完成 //channel内按顺序自增的 long deliveryTag = message.getMessageProperties().getDeliveryTag(); // 签收,非批量模式 try{ if(deliveryTag%2 == 0){ //签收货物 channel.basicAck(deliveryTag,false); } else { //退货 channel.basicNack(deliveryTag,false,false); //channel.basicReject(); 同上,只是不可批量 //没有签收货物 } } catch (Exception e) { //网络中断 } }
延时队列关闭订单模拟
-
创建Exchange Queue Binding
@Configuration public class MyMQConfig { //死信队列 @Bean public Queue orderDelayQueue(){ /* Queue(String name, 队列名字 boolean durable, 是否持久化 boolean exclusive, 是否排他 boolean autoDelete, 是否自动删除 Map<String, Object> arguments) 属性 */ HashMap<String, Object> arguments = new HashMap<>(); arguments.put("x-dead-letter-exchange", "order-event-exchange"); arguments.put("x-dead-letter-routing-key", "order.release.order"); arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟 Queue queue = new Queue("order.delay.queue", true, false, false, arguments); return queue; } //普通队列 @Bean public Queue orderReleaseOrderQueue(){ Queue queue = new Queue("order.release.order.queue", true, false, false); return queue; } //TopicExchange @Bean public Exchange orderEventExchange(){ /* * String name, * boolean durable, * boolean autoDelete, * Map<String, Object> arguments * */ return new TopicExchange("order-event-exchange", true, false); } @Bean public Binding orderCreateOrderBinding(){ /* * String destination, 目的地(队列名或者交换机名字) * DestinationType destinationType, 目的地类型(Queue、Exhcange) * String exchange, * String routingKey, * Map<String, Object> arguments * */ return new Binding("order.delay.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.create.order", null); } @Bean public Binding orderReleaseOrderBinding(){ return new Binding("order.release.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.order", null); } } 创建订单
@GetMapping("/test/createOrder") public String creatOrderTest(){ OrderEntity entity = new OrderEntity(); entity.setOrderSn(UUID.randomUUID().toString()); entity.setModifyTime(new Date()); rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",entity); return "ok"; } 消费者,接收过期订单
@RabbitListener(queues = "order.release.order.queue") public void listener(OrderEntity entity, Channel channel, Message message) throws IOException { System.out.println("收到过期的订单信息:准备关闭订单"+entity.getOrderSn()); channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); }
如何保证消息可靠性
丢失
-
网络问题,消息没到服务器
- try-catch
- 日志记录
- 定期发送
-
消息抵达broker,未持久化宕机
publisher也加入确认回调机制
-
自动ACK状态,消费者收到没来及处理宕机
手动ACK
重复
-
消费成功,事务提交,ack时机器宕机,Broker消息重新变为ready
-
消费失败,重试机制 允许
业务逻辑设计幂等性
防重表
消息redelivered属性
积压
-
消费者宕机
-
消费能力不足
-
发送流量太大
上线更多消费者
上线专门的队列消费服务,批量取出消息,记录数据库,离线慢慢处理
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异