springboot整合RabbitMQ延时队列

延时队列

  1. 意义

    ​ 延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。 例如: 当你一个订单执行时,你未能即时的支付,需要在30分钟后自动判断是否支付成功,这时候就需要延时队列,简单来说就是一个可动态变化的定时器。当生成订单时就会触发的定时器

  2. 方式

    死信交换器(DLX)

    DLX是Dead-Letter-Exchange的缩写,全称死信交换器。成为死信队列后,可以被重新发送到另外一个交换器中,这个交换器就是DLX,绑定DLX到队列称为死信队列。注意,死信队列和死信交换器不一样哦。

    成为死信一般由以下几种情况:

    • 消息被拒绝 (basic.reject or basic.nack) 且带 requeue=false 参数

    • 消息的TTL-存活时间已经过期

    • 队列长度限制被超越(队列满)

    DLX和一般的交换器没有区别,可以声明在任何的队列上,理解为队列的一个属性吧。当这个队列有死信时会根据设置自动的将死信重新发布到设置的DLX上进行消费。这个消费了死信的队列称之为死信队列,并不是绑定了DLX的队列是死信队列,大家要区分清楚。

    通过在队列里设置 x-dead-letter-exchange 参数来声明DLX,

    如果当前DLX是 direct 类型还要 声明 x-dead-letter-routing-key 参数来指定路由键,如果没有指定,则使用原队列的路由键。

    过期时间(TTL)

    TTL是Time To Live的缩写, 也就是生存时间。RabbitMq支持对消息和队列设置TTL,对消息这设置是在发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动清除。

    如果两种方式一起使用消息对TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s的生效。

    默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被消息,否则丢弃。

    设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。

    设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0

  3. 添加Maven依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    
  4. application.yml( application.properties)

    spring:
      rabbitmq:
        host: 127.0.0.1(本地安装就是本地ip,Linux就是虚拟机ip)
        port: 5672(一般都是这个)
        username: guest(默认的用户名)
        password: guest(默认密码)
    
    

    简单测试

    ​ 1.简单来说就是两个Queue,两个DirectExchange ,两个Binding. with后面的是路由

    package com.example.rdemo.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.core.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    @Slf4j
    public class DelayRabbitConfig {
    
    
        /**
         * 延迟队列 TTL 名称
         */
        private static final String ORDER_DELAY_QUEUE = "user.order.delay.queue";
        /**
         * DLX,dead letter发送到的 exchange
         * 延时消息就是发送到该交换机的
         */
        public static final String ORDER_DELAY_EXCHANGE = "user.order.delay.exchange";
        /**
         * routing key 名称
         * 具体消息发送在该 routingKey 的
         */
        public static final String ORDER_DELAY_ROUTING_KEY = "order_delay";
    
        public static final String ORDER_QUEUE_NAME = "user.order.queue";
        public static final String ORDER_EXCHANGE_NAME = "user.order.exchange";
        public static final String ORDER_ROUTING_KEY = "order";
    
        /**
         * 延迟队列配置
         * <p>
         * 1、params.put("x-message-ttl", 5 * 1000);
         * 第一种方式是直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活,(当然二者是兼容的,默认是时间小的优先)
         * 2、rabbitTemplate.convertAndSend(book, message -> {
         * message.getMessageProperties().setExpiration(2 * 1000 + "");
         * return message;
         * });
         * 第二种就是每次发送消息动态设置延迟时间,这样我们可以灵活控制
         **/
        
        /**
         *	普通队列
    	 */
        @Bean
        public Queue delayOrderQueue() {
            Map<String, Object> params = new HashMap<>();
            // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
            params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME);
            // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
            params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY);
            return new Queue(ORDER_DELAY_QUEUE, true, false, false, params);
        }
        
        @Bean
        public DirectExchange orderDelayExchange() {
            return new DirectExchange(ORDER_DELAY_EXCHANGE);
        }
        
         /**
         *	普通队列
    	 */
        @Bean
        public Binding dlxBinding() {
            return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY);
        }
    
        /**
         * 死信接收队列
         *
         * @return
         */
        @Bean
        public Queue orderQueue() {
            return new Queue(ORDER_QUEUE_NAME, true);
        }
       
        /**
         * 死信接收交换机
         *
         * @return
         */
        @Bean
        public TopicExchange orderTopicExchange() {
            return new TopicExchange(ORDER_EXCHANGE_NAME);
        }
    
        /**
         * 死信交换机绑定消费队列
         *
         * @return
         */
        @Bean
        public Binding orderBinding() {
            // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
            return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY);
        }
    
    }
    
    
    
    1. 实体类

      package com.example.rdemo.pojo;
      
      import lombok.Data;
      import java.io.Serializable;
      
      @Data
      public class Order implements Serializable {
      
      
          private static final long serialVersionUID = -2221214252163879885L;
      
          private String orderId; // 订单id
      
          private Integer orderStatus; // 订单状态 0:未支付,1:已支付,2:订单已取消
      
          private String orderName; // 订单名字
      }
      
      
      
    2. 发送者

      package com.example.rdemo.config;
      
      
      import com.example.rdemo.pojo.Order;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.amqp.core.AmqpTemplate;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Component;
      
      import java.util.Date;
      
      @Component
      @Slf4j
      public class DelaySender {
      
          @Autowired
          private AmqpTemplate amqpTemplate;
      
          public void sendDelay(Order order) {
              log.info("【订单生成时间】" + new Date().toString() +"【1分钟后检查订单是否已经支付】" + order.toString() );
              //第一个参数是前面DelayRabbitConfig的交换机名称 第二个参数的路由名称 第三个参数是传递的参数 第四个参数是配置属性
              this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> {
                  // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
                  message.getMessageProperties().setExpiration(1 * 1000 * 60 + "");
                  return message;
              });
          }
      }
      
      
    3. 接收者

      package com.example.rdemo.config;
      
      import com.example.rdemo.pojo.Order;
      import com.rabbitmq.client.Channel;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.amqp.core.Message;
      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;
      
      import java.util.Date;
      
      @Component
      @Slf4j
      public class DelayReceiver {
      
          //对应实现队列
          @RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME})
          public void orderDelayQueue(Order order, Message message, Channel channel) {
              log.info("###########################################");
              log.info("【orderDelayQueue 监听的消息】 - 【消费时间】 - [{}]- 【订单内容】 - [{}]",  new Date(), order.toString());
              if(order.getOrderStatus() == 0) {
                  order.setOrderStatus(2);
                  log.info("【该订单未支付,取消订单】" + order.toString());
              } else if(order.getOrderStatus() == 1) {
                  log.info("【该订单已完成支付】");
              } else if(order.getOrderStatus() == 2) {
                  log.info("【该订单已取消】");
              }
              log.info("###########################################");
          }
      }
      
      
      
    4. 测试

      package com.example.rdemo.controller;
      
      
      
      import com.example.rdemo.config.DelaySender;
      import com.example.rdemo.pojo.Order;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.concurrent.DelayQueue;
      
      @RestController
      public class TestController {
      
          @Autowired
          private DelaySender delaySender;
      
          @GetMapping("/sendDelay")
          public Object sendDelay() {
              Order order1 = new Order();
              order1.setOrderStatus(0);
              order1.setOrderId("123456");
              order1.setOrderName("小米6");
      
              Order order2 = new Order();
              order2.setOrderStatus(1);
              order2.setOrderId("456789");
              order2.setOrderName("小米8");
      
              delaySender.sendDelay(order1);
              delaySender.sendDelay(order2);
              return "ok";
          }
      }
      
      
      

      6.运行

在这里插入图片描述
7.结果

在这里插入图片描述

  一分钟后

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值