Spring Boot学习 RabbitMQ 消息队列 基本概念 web端操作 SpringBoot整合
4.延时队列 实现定时任务
设置队列TTL的实现方式:
此次实现方式:使用同一个交换机的不同路由键实现
代码实现:
4.1 创建组件
package com.rwp.gulimail.order.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MyMQConfig {
/**
* 添加@Bean后,springboot启动是如果mq中没有相关的队列,则会先创建队列
* @return
*/
@Bean
public Queue orderDelayQueue(){
Map<String,Object> argument = new HashMap<>();
//String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments
//创建延时队列--死信队列
argument.put("x-dead-letter-exchange","order-event-exchange");
argument.put("x-dead-letter-routing-key","order.release.order");
argument.put("x-message-ttl",60000);
Queue queue = new Queue("order.delay.order.queue", true, false, false, argument);
return queue;
}
@Bean
public Queue orderReleaseOrderQueue(){
Queue queue = new Queue("order.release.order.queue", true, false, false);
return queue;
}
@Bean
public Exchange orderEventExchange(){
//String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
Exchange exchange = new TopicExchange("order-event-exchange",true,false);
return exchange;
}
@Bean
public Binding orderCreateOrderBinding(){
//String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments
Binding binding = new Binding("order.delay.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order",
null);
return binding;
}
@Bean
public Binding orderReleaseOrderBinding(){
//String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments
Binding binding = new Binding("order.release.order.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",
null);
return binding;
}
}
4.2测试
发送消息:
@Resource
private RabbitTemplate rabbitTemplate;
@GetMapping("/create/order")
@ResponseBody
public OrderEntity createOrderTest(){
OrderEntity orderEntity = new OrderEntity();
orderEntity.setOrderSn(new Date().toString());
rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",orderEntity);
return orderEntity;
}
监听消息:
@RabbitListener(queues = "order.release.order.queue")
public void listener(OrderEntity entity, Message message, Channel channel) throws IOException {
System.out.println("收到过期的订单信息,准备关闭订单"+entity);
System.out.println("当前时间:"+new Date());
//手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
结果:
- 发送请求后,一分钟内队列 order.delay.order.queue中会存在一个消息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzhN61jh-1600669854442)(D:\学习资料\笔记\project-gulimail\image-20200918134450327.png)]
- 一分钟后消息到达队列order.release.order.queue并被消费,实现延时队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLVy64kl-1600669854446)(D:\学习资料\笔记\project-gulimail\image-20200918134417437.png)]
5.保证消息可靠性
5.1 消息丢失产生的原因和解决方案
原因一:消息发出去,由于网络问题没有抵达服务器
-
做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可以记录到数据库,采用定期扫描重发的方式
-
做好日志记录,每个消息状态是否都被服务器收到都应该记录
-
做好定期重发
-
CREATE TABLE mq_message( message_id varhcar(32) not null, content text, #json序列化后的消息体 to_exchange varchar(255) default null, #发送消息时的三个参数 routing_key varchar(255) default null, class_type varchar(255) default null, message_status int(1) default '0' comment '0-新建 1-已发送 2-错误抵达 3-已抵达', create_time datetime, update_time datetime, PRIMARY KEY (message_id) )ENGINE=INNODB DEFAULT CHARSET=utf8mb4
原因二:消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功.此时Broker尚未完成持久化,发生了宕机
- publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态
- 在returnedMessage回调方法中(没有到达Broker时会调用),修改数据库中保存信息的状态
原因三:自动ack的状态下.消费者收到消息,但没来得及消费后宕机
- consumer一定开启手动ACK,消费成功才移除,失败或没来得及处理就noACK并重新进队
总结:
- publish(ReturnCallback)和consumer(手动ACK)同时做好消息确认机制
- 数据库进行所有消息的持久化和定期检查重新发送
5.2 消息重复产生的原因和解决方案
原因:消息消费成功,事务已经提交,手动ack时,机器宕机.导致没有ack成功,Broker的消息从unack变为ready,并发送给其他消费者
解决方案:
- 消费者的业务消费接口应该设计为幂等的.例如添加修改的标识位
- 使用防重表,发送消息每一个都有业务的唯一标识,处理过就不用处理
- rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的消息,而不是第一次投递
5.3 消息积压产生的原因和解决方案
原因:
- 消费者宕机积压
- 消费者消费能力不足积压
- 发送者发送流量太大
解决方案:
- 增加更多的消费者,进行正常消费
息,修改数据库消息状态 - 在returnedMessage回调方法中(没有到达Broker时会调用),修改数据库中保存信息的状态
原因三:自动ack的状态下.消费者收到消息,但没来得及消费后宕机
- consumer一定开启手动ACK,消费成功才移除,失败或没来得及处理就noACK并重新进队
总结:
- publish(ReturnCallback)和consumer(手动ACK)同时做好消息确认机制
- 数据库进行所有消息的持久化和定期检查重新发送
5.2 消息重复产生的原因和解决方案
原因:消息消费成功,事务已经提交,手动ack时,机器宕机.导致没有ack成功,Broker的消息从unack变为ready,并发送给其他消费者
解决方案:
- 消费者的业务消费接口应该设计为幂等的.例如添加修改的标识位
- 使用防重表,发送消息每一个都有业务的唯一标识,处理过就不用处理
- rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的消息,而不是第一次投递
5.3 消息积压产生的原因和解决方案
原因:
- 消费者宕机积压
- 消费者消费能力不足积压
- 发送者发送流量太大
解决方案:
- 增加更多的消费者,进行正常消费
- 上线专门的队列消息服务,将消息先批量取出来,记录数据库,离线时慢慢处理