RabbitMQ延时队列实现定时任务

  之前介绍了关于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中去。如果一个消息满足下面的条件,就会进死信路由

  1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false,也就是说不会被再次放入到队列中
  2. 消息的TTL到了,消息过期了
  3. 队列的长度满了,排在前面的消息

  我们既可以控制消息在一段时间后变成死信,也可以控制变成死信的消息被路由到指定的交换机中,结合二者就可以实现延时队列;延时队列的实现方式有两种:设置队列的过期时间设置消息的过期时间
RabbitMQ延时队列实现定时任务_第1张图片
RabbitMQ延时队列实现定时任务_第2张图片
  虽然有两种方式,但是推荐使用给队列设置过期时间,如果给消息设置过期时间,由于RabbitMQ采用的是惰性检查机制,就是说如果第一个还没过期,但是后面的已经过期了,后面的得等到前面的过期了队列才会去丢弃它

  接下来就使用代码演示延迟队列的使用,简单的小demo,其流程大致如下图
RabbitMQ延时队列实现定时任务_第3张图片
  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;
    }
}

  接下来就启动测试,从结果上看,20秒后所有的消息队列都被移除到另一个队列里了
RabbitMQ延时队列实现定时任务_第4张图片

你可能感兴趣的:(延迟队列,rabbitmq)