RabbitMQ延时队列

场景: 比如未付款订单,超过一定时间后,系统自动取消订单并释放占有物品。常用解决方案:.spring的schedule定时任务轮询数据库缺点:消耗系统内存、增加了数据库的压力、存在较大的时间误差解决: rabbitmq的消息TTL和死信Exchange结合
在这里插入图片描述

消息的TTL (Time To Live)

消息的TTL就是消息的存活时间
RabbitMQ可以对队列和消息分别设置TTL
对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-tt属性来设置时间,两者是一样的效果。

死信路由Dead Letter Exchanges(DLX)

一个消息在满足如下务件下,会进死信路由,这里是路由而不是队列,一个路由可以对应很多a(什么是死信)一个消息被Consumer拒收了,开Ereeci的参数里requeue是false,也就是说不会被再次放在队列里,被其他消费者使用。(basicreject/ basic.nack) requeue=false上面的消息的TTL到了,消息过期了。队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某一个指定的交换机,结合二者,其实就可以实现一个延时队列

使用消息TTL+死信路由就可以实现延时队列
实现方式1:队列设置所有消息的过期时间
在这里插入图片描述
这里实现的逻辑是生产者通过指定路由键给交换机发送一条消息,交换机将消息转发给队列,这个队列假设设置了5分钟的TTL也就是5分钟后进入队列的消息就会失效,同时,这个队列还指定了死信路由,而这个死信路由又会将失效的消息转发给指定的死信交换机,同时由死信交换机转发给专门接收过期消息的队列,在消费端又有指定的服务监听这个队列,所以这里的TTL5分钟的值也就是队列的延时时间,那么也就完成了消息的延时推送!

实现方式2:给每条消息单独设置过期时间
在这里插入图片描述
这个的核心原理和上面的队列设置TTL是差不多的,但是这里不建议使用消息设置TTL来实现延时队列!推荐使用队列设置过期时间,
原因: RabbitMq采用惰性检查机制,也就是懒检查机制,比如消息队列中存放了多条消息,第一条是5分钟过期,第二条是1分钟过期,第三条是1秒钟过期,按照正常的过期逻辑,应该是1秒过期的先排出这个队列,进入死信队列中,但是实际RabbitMQ是先拿第一条消息,也就是5分钟过期的,一看5分钟还没到过期时间,然后等待5分钟会将第一条消息拿出来,放入死信队列,这里就会出现问题,第二条设置1分钟的和第三条设置1秒钟的消息必须要等待第一条5分钟过期后才能过期,等待第一条消息过期5分钟了,拿第二条、三条的时候都不需要判断就已经过期了,直接就放入死信队列中,所以第二条、三条需要等待第一条消息过了5分钟才能过期,这样的延时根本就没产生对应的效果!

代码演示

1.导入相关依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2.主启动类开启RabbitMQ

@EnableRabbit

3.创建RabbitMQ配置类

@Slf4j
@Configuration
public class MyMQConfig {

    /**
     * 容器中的 Binding,Queue Exchange 都会自动创建,(RabbitMq没有的情况)
     * RabbitMQ 只要有 @Bean属性发生变换也不会覆盖
     * @return
     */
    @Bean
    public Queue orderDelayQueue() {//死信队列
        Map<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);
        Queue queue = new Queue("order.delay.queue", true, false, false,arguments);
        return queue;
    }
    @Bean
    public Queue orderReleaseOrderQueue() {//普通队列
        Queue queue = new Queue("order.release.queue", true, false, false);
        return queue;
    }
    @Bean
    public Exchange orderEventExchange() {//交换机
       return new TopicExchange("order-event-exchange",true,false);
    }
    @Bean
    public Binding orderCreateOrderBingding() {//绑定关系-死信队列
        return new Binding("order.delay.queue", Binding.DestinationType.QUEUE,"order-event-exchange","order.create.order",null);
    }

    @Bean
    public Binding orderReleaseOrderBingding() {//绑定关系-普通队列
        return new Binding("order.release.queue", Binding.DestinationType.QUEUE,"order-event-exchange","order.release.order",null);
    }

	//监听由死信队列过期转到普通队列的消息
    @RabbitListener(queues={"order.release.queue"})
    public void listener(OrderEntity entity, Channel channel, Message message) throws IOException {
        log.info("收到过期的订单信息,准备关闭订单"+entity.getOrderSn());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }
}

4.编写消息发送接口

	@Autowired
    RabbitTemplate rabbitTemplate;

	@ResponseBody
    @GetMapping("/sendTest")
    public String sendTest(){
        //new CorrelationData(UUID.randomUUID().toString())指定消息的唯一id
        //项目实际情况还会将消息的唯一id存入数据库中,用作后期队列中的消息消费情况做对比
        String id= UUID.randomUUID().toString();
        OrderEntity entity=new OrderEntity();
        entity.setOrderSn(id);
        entity.setModifyTime(new Date());
        log.info("消息发送中...消息唯一id"+id);
        rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", entity);
        return "ok";
    }

5.这里还有一块配置,就是MQ的消息确认机制,消息系列化类型

@Configuration
public class MyRabbitConfig {

    //讲对象序列化为JSON
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }

    @Autowired
    RabbitTemplate rabbitTemplate;

    //定制RabbitTemplate
    @PostConstruct//MyRabbitConfig对象创建完成后执行这个初始化方法
    public void initRabbitTemplate(){
        //设置发送消息确认回调p->b
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){

            /**
             * @param correlationData  当前消息的唯一关联数据  ,这个是消息的唯一id
             * @param ack 消息是否成功收到
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm...correlationData=>"+correlationData+"----------ack==>"+ack+"--------cause  ==>"+cause);
            }
        });


        //设置消息抵达队列的确认回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定的队列,就会触发这个失败回调
             * @param message   投递失败的消息
             * @param replyCode  回复的状态码
             * @param replyText  回复的文本内容
             * @param exchange  消息发送的交换机
             * @param routingKey  消息走的路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("returnedMessage-->"+message+"\nreplyCode-->"+replyCode+"\nexchange-->"+exchange+"\nroutingKey-->"+routingKey);
            }
        });
    }
}

6.application配置

  rabbitmq:
    host: 192.168.0.177
    virtual-host: /
    port: 5672
    #开启发送端确认p->b
    publisher-confirms: true
    #开启消息抵达队列的确认 e->q
    publisher-returns: true
    #只要消息的大队列,以异步的方式优先回调我们这个returnconfirm
    template:
      mandatory: true
    #手动签收消息
    listener:
      simple:
        acknowledge-mode: manual

这里的第5、6步之前也有写过相关文章,有详细介绍RabbitMQ消息确认机制-可靠抵达

7.测试效果
服务启动后,只要当前服务有链接RabbitMQ,也就是有监听器存在,就会自动创建队列、交换机、绑定关系!
在这里插入图片描述
检查消息队列的消息
在这里插入图片描述
消息数都是0
先用浏览器发送3个sendTest请求

查看队列消息
在这里插入图片描述
在这里插入图片描述
等待1分钟
在这里插入图片描述
消息差不多延迟1分钟到达消费端!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员劝退师-TAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值