之前介绍了关于RabbitMQ和SpringBoot整合RabbitMQ,今天聊聊RabbitMQ的延时队列,RabbitMQ的延时队列可用于保证事务的最终一致性问题
例如我们有这么一个场景,未付款订单,超过30分钟后,系统自动取消订单并释放占有物品,寻常的解决的方案多半是使用spring的schedule定时任务去轮询数据库,然后进行判断操作。但是,假如订单是在第2分钟下的,现在每30分钟轮询一次,第一次轮询时这个订单没有超过30分钟不能进行过期,在32分钟时这个订单就该过期,但是我们使用的是spring的定时任务,距离下一扫描还有28分钟,这个时候用户依旧可以对订单进行操作,相当于这个订单存活了58分钟,远远超过了我们规定的时间。这种情况我们可以考虑使用RabbitMQ的消息TTL和死信Exchange结合的方式
首先我们得知道什么是消息的TTL
消息的TTL(TIme To Live),就是消息的存活时间,RabbitMQ可以对队列和消息分别设置TTL。
对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信
如果队列和消息都设置TTL,那么会取小的,所以一个消息被路由到不同的队列中,这个消息的死亡时间可能不一样
接下来再看看什么是死信路由
死信路由DLX(Dead Letter Exchanges)就是一种普通的exchange,和创建其他的路由没啥两样,只是在某一个设置了Dead Letter Exchange的队列中有消息过期了就会自动触发消息的转发,发送到Dead Letter Exchange中去。如果一个消息满足下面的条件,就会进死信路由
我们既可以控制消息在一段时间后变成死信,也可以控制变成死信的消息被路由到指定的交换机中,结合二者就可以实现延时队列;延时队列的实现方式有两种:设置队列的过期时间和设置消息的过期时间
虽然有两种方式,但是推荐使用给队列设置过期时间,如果给消息设置过期时间,由于RabbitMQ采用的是惰性检查机制,就是说如果第一个还没过期,但是后面的已经过期了,后面的得等到前面的过期了队列才会去丢弃它
接下来就使用代码演示延迟队列的使用,简单的小demo,其流程大致如下图
1. 导入RabbitMQ的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2. 创建交换机、队列等,Spring对这些进行了简化,可以通过一个配置类在启动时就自动创建,但是如果创建了就不会重复创建,需要手动删除之前才会重新创建
@Configuration
public class MyMQConfig {
@RabbitListener(queues = "order.release.order.queue")
public void listener(Object o, Channel channel, Message message) throws IOException {
System.out.println("过期的消息 ===> " + new String(message.getBody()) + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
/**
* 死信队列
*/
@Bean
public Queue orderDelayQueue() {
//死信队列的参数
Map<String, Object> params = new HashMap<>();
params.put("x-dead-letter-exchange", "order-event-exchange");//死信路由名称
params.put("x-dead-letter-routing-key", "order.release.order");//死信路由的路由键
params.put("x-message-ttl", 20000);//消息过期时间(单位:毫秒)
return new Queue("order.delay.queue", true, false, false, params);
}
@Bean
public Queue orderReleaseOrder() {
return new Queue("order.release.order.queue", true, false, false);
}
@Bean
public Exchange orderEventExchange() {
return new TopicExchange("order-event-exchange", true, false);
}
@Bean
public Binding orderCreateOrderBinding() {
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);
}
}
3. 测试接口
@RestController
public class RabbitDemoController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/test/createOrder")
public R createOrder() {
System.out.println("创建订单===> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Order order = new Order();
order.setOrderSn(UUID.randomUUID().toString());
// 给MQ发消息
rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order);
return R.ok();
}
@Data
public static class Order implements Serializable {
private String orderSn;
private Date createDate;
}
}