五、RabbitMQ死信队列和延迟队列

RabbitMQ死信队列和延迟队列

RabbitMQ死信队列

死信队列及TTL

什么是TTL?

  • time to live 消息存活时间
  • 如果消息在存活时间内未被消费,则会别清除
  • RabbitMQ支持两种ttl设置
    • 单独消息进行配置ttl
    • 整个队列进行配置ttl(居多)

什么是rabbitmq的死信队列?

  • 没有被及时消费的消息存放的队列

什么是rabbitmq的死信交换机?

  • Dead Letter Exchange(死信交换机,缩写:DLX)当消息成为死信后,会被重新发送到另一个交换机,这个交换机就是DLX死信交换机。

消息有哪几种情况成为死信

  • 消费者拒收消息**(basic.reject/ basic.nack)**,并且没有重新入队 requeue=false
  • 消息在队列中未被消费,且超过队列或者消息本身的过期时间TTL(time-to-live)
  • 队列的消息长度达到极限
  • 结果:消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列

这里就是一条消息要么正常消费要么超时进入死信队列,不要误以为两个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpy4aX1O-1620189315062)(死信队列图示.png)]

使用RabbitMQ控制台做一个死信队列测试
  1. 先创建一个死信交换机(名为死信其实和普通交换机没任何区别)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUgMjRLK-1620189315065)( 死信交换机创建.png)]

  2. 创建一个死信队列(同样名为死信其实和普通队列没任何区别)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upUjM1ZV-1620189315067)( 创建死信队列.png)]

  3. 将死信交换机和死信队列进行绑定

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jl303adJ-1620189315071)( 死信交换机绑定死信队列.png)]

  4. 新建普通队列,设置过期时间、指定死信交换机

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9L392DS-1620189315074)( 普通设置超时时间和绑定死信交换机的队列.png)]

  5. 点进product_queue队里中有一栏pulbish message可以用来模拟消息入列

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdxLNMum-1620189315076)( 死信队列消息发送测试.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-toZOMhdw-1620189315078)(发送死信消息状态1.png)]

10秒之后

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLp8LUtG-1620189315079)(发送死信消息状态2.png)]

如期10秒后进入死信队列,接下来可以由绑定这个死信队列的消费者进行死信处理。

Rabbit的延迟队列

什么是延迟队列?

  • 一种带有延迟功能的消息队列,Producer 将消息发送到消息队列 服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到 Consumer 进行消费,该消息即定时消息

使用场景

  • 通过消息触发一些定时任务,比如在某一固定时间点向用户发送提醒消息
  • 用户登录之后5分钟给用户做分类推送、用户多少天未登录给用户做召回推送;
  • 消息生产和消费有时间窗口要求:比如在天猫电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条 延时消息。这条消息将会在 30 分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。 如支付未完成,则关闭订单。如已完成支付则忽略

业界的一些实现方式

  • 定时任务高精度轮训
  • 采用RocketMQ自带延迟消息功能
  • RabbitMQ本身是不支持延迟队列的,怎么办?
    • 结合死信队列的特性,就可以做到延迟消息
Rabbit延时队列样例

Rabbit的延时队列是普通队列和死信队列结合做出。

  1. 在配置类中配置好死信交换机、死信队列、普通交换机、普通队列

    // 新商家审核 -> new_merchant_queue -> 死信交换机 -> 死信队列
    @Configuration
    public class RabbitMQConfig {
    
        // 死信队列
        public static final String LOCK_MERCHANT_DEAD_QUEUE = "lock_merchant_dead_queue";
    
        // 死信交换机
        public static final String LOCK_MERCHANT_DEAD_EXCHANGE = "lock_merchant_dead_exchange";
    
        // 进入死信队列的路由key
        public static final String LOCK_MERCHANT_ROUTING_KEY = "lock_merchant_routing_key";
    
    
        /**
         * 创建死信交换机
         * @return
         */
        @Bean
        public Exchange lockMerchantDeadExchange(){
            //return new TopicExchange(LOCK_MERCHANT_DEAD_EXCHANGE,true,false);
            return ExchangeBuilder.topicExchange(LOCK_MERCHANT_DEAD_EXCHANGE).durable(true).build();
        }
    
        /**
         * 创建死信队列
         * @return
         */
        @Bean
        public Queue lockMerchantDeadQueue(){
            return QueueBuilder.durable(LOCK_MERCHANT_DEAD_QUEUE).build();
        }
    
        /**
         * 绑定死信交换机和死信队列
         * @return
         */
        @Bean
        public Binding lockMerchantBinding(){
            return new Binding(LOCK_MERCHANT_DEAD_QUEUE,Binding.DestinationType.QUEUE,LOCK_MERCHANT_DEAD_EXCHANGE,LOCK_MERCHANT_ROUTING_KEY,null);
        }
    
        // 普通队列
        public static final String NEW_MERCHANT_QUEUE = "new_merchant_queue";
    
        // 普通topic交换机
        public static final String NEW_MERCHANT_EXCHANGE = "new_merchant_exchange";
    
        // 进入普通队列的路由key
        public static final String NEW_MERCHANT_ROUTING_KEY = "new_merchant_routing_key";
    
        /**
         * 创建普通交换机
         * @return
         */
        @Bean
        public Exchange newMerchantDeadExchange(){
            //return new TopicExchange(LOCK_MERCHANT_DEAD_EXCHANGE,true,false);
            return ExchangeBuilder.topicExchange(NEW_MERCHANT_EXCHANGE).durable(true).build();
        }
    
        /**
         * 创建普通队列 (这里普通队列绑定死信交换机配置以及死信配置是关键)
         * @return
         */
        @Bean
        public Queue newMerchantDeadQueue(){
            HashMap<String, Object> hashMap = new HashMap<>(4);
            // 消息过期进入死信队列
            hashMap.put("x-dead-letter-exchange",LOCK_MERCHANT_DEAD_EXCHANGE);
            // 消息过期后,进入到死信队列的路由key
            hashMap.put("x-dead-letter-routing-key",LOCK_MERCHANT_ROUTING_KEY);
            hashMap.put("x-message-ttl",10000);
    
            return QueueBuilder.durable(NEW_MERCHANT_QUEUE).withArguments(hashMap).build();
        }
    
        /**
         * 绑定普通交换机和普通队列
         * @return
         */
        @Bean
        public Binding newMerchantBinding(){
            return new Binding(NEW_MERCHANT_QUEUE,Binding.DestinationType.QUEUE,NEW_MERCHANT_EXCHANGE,NEW_MERCHANT_ROUTING_KEY,null);
        }
    
    }
    
  2. 添加一个测试接口

    @RestController
    @RequestMapping("/api/admin/merchant")
    public class MerchantAccountController {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @GetMapping("check")
        public Object check(){
    
            // todo 修改数据库的商家账号状态
    
            rabbitTemplate.convertAndSend(RabbitMQConfig.NEW_MERCHANT_EXCHANGE,RabbitMQConfig.NEW_MERCHANT_ROUTING_KEY,"商家账号通过审核");
    
            HashMap<String,String> hashMap = new HashMap<>(16);
            hashMap.put("code","0");
            hashMap.put("msg","账号审核通过,请10秒内确认上传");
            return hashMap;
        }
    
    }
    
  3. 写一个死信队列的监听处理类,为测试死信队列效果先不做确认操作

    @Component
    @RabbitListener(queues = "lock_merchant_dead_queue")
    public class MerchantMQListener {
    
        @RabbitHandler
        public void messageHandler(String body, Message message, Channel channel) throws IOException {
            long msgTag = message.getMessageProperties().getDeliveryTag();
            System.out.println("msgTag="+msgTag);
            System.out.println("message="+message.toString());
            System.out.println("body="+body);
    
            // 告诉broker,消息已被确认
    //        channel.basicAck(msgTag,false);
    
        }
    
    }
    
  4. 测试结果

    访问测试接口:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKvIgVr3-1620189315081)(服务启动访问测试接口.png)]

在去看控制台:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RN56VypT-1620189315082)(先进入普通队列.png)]

消息先进入普通队列,10秒之后进入死信队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8sRjPQPi-1620189315083)(进入死信队列.png)]

这里正是因为我们把死信队列的监听处理类中的确认消息注释掉所以才能看到Unacked 为1,说明测试正确,10秒后经过死信交换机进入到了死信队列,并且我们写的监听也有相应的调用,打印了一些我们在监听处理类做的输出信息

2021-04-28 13:51:51.631  INFO 33616 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-04-28 13:51:51.632  INFO 33616 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
msgTag=1
message=(Body:'商家账号通过审核' MessageProperties [headers={x-first-death-exchange=new_merchant_exchange, x-death=[{reason=expired, count=1, exchange=new_merchant_exchange, time=Tue Apr 27 23:15:05 CST 2021, routing-keys=[new_merchant_routing_key], queue=new_merchant_queue}], x-first-death-reason=expired, x-first-death-queue=new_merchant_queue}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=lock_merchant_dead_exchange, receivedRoutingKey=lock_merchant_routing_key, deliveryTag=1, consumerTag=amq.ctag-o6qXvE7IzHuK0aXDOR-FMg, consumerQueue=lock_merchant_dead_queue])
body=商家账号通过审核

如果有强迫症可以将MerchantMQListener类中的确认信息放开,重启服务把那条进入死信队列的消息消费掉就行了,我就是这样做的,启动之后都不需要看RabbitMQ控制台,只要看到启动后有那条死信消息的打印就知道确认成功了,再看控制台就都变回0了。

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值