1.什么是延时队列:
首先是一种队列,以为着内部的元素是有序的,元素的出队和入队是有方向性的,元素从一端进入从另一端取出。
其次,延时队列,最重要的特性就体现在它的延时属性上,跟普通的队列不一样的是,普通队列中的元素总是等着希望被早点取出处理,而延时队列中的元素则是希望被在指定时间得到取出和处理,所以延时队列中的元素是都是带时间属性的,通常来说是需要被处理的消息或者任务。
简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

2.延时队列使用场景:
1.订单在十分钟之内未支付则自动取消。
2.新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
3.账单在一周内未支付,则自动结算。
4.用户注册成功后,如果三天内没有登陆则进行短信提醒。
5.用户发起退款,如果三天内没有得到处理则通知相关运营人员
特点:
这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,延时队列可以解决很多特定场景下,带时间属性的任务需求,接下来介绍如何用RabbitMQ来实现延时队列

3.RabbitMQ中的TTL
RabbitMQ中的一个高级特性——TTL(Time To Live)
表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒,创建队列的时候设置队列的“x-message-ttl”属性.

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 6000);
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);

这样所有被投递到该队列的消息都最多不会存活超过6s,那么一旦消息过期,就会被队列丢弃,另外,还需要注意的一点是,如果不设置TTL,表示消息永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。

4.利用rabbitmq实现延迟队列

centos安装Rabbitmq延迟插件 rabbitmq延迟队列原理_使用场景


先理解几个名词:

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。

Queue:消息队列载体,每个消息都会被投入到一个或多个队列。

Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。

Routing Key:路由关键字,exchange根据这个关键字进行消息投递。

channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

5.代码实现

引入依赖

<!--rabbit mq 队列-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置文件配置连接信息

spring:
  application:
    name: xxxx
  rabbitmq:
    host: 192.168.127.129
    port: 5672
    username: admin
    password: StrongPassword
    publisher-confirms: true
server:
  port: 6001

先写一个配置类:

@Configuration
public class RabbitMQConfig {
    public static final String DELAY_EXCHANGE_NAME = "delay.queue.demo.business.exchange";
    public static final String DELAY_QUEUEA_NAME = "delay.queue.demo.business.queuea";
    public static final String DELAY_QUEUEB_NAME = "delay.queue.demo.business.queueb";
    public static final String DELAY_QUEUEA_ROUTING_KEY = "delay.queue.demo.business.queuea.routingkey";
    public static final String DELAY_QUEUEB_ROUTING_KEY = "delay.queue.demo.business.queueb.routingkey";
    public static final String DEAD_LETTER_EXCHANGE = "delay.queue.demo.deadletter.exchange";
    public static final String DEAD_LETTER_QUEUEA_ROUTING_KEY = "delay.queue.demo.deadletter.delay_10s.routingkey";
    public static final String DEAD_LETTER_QUEUEB_ROUTING_KEY = "delay.queue.demo.deadletter.delay_60s.routingkey";
    public static final String DEAD_LETTER_QUEUEA_NAME = "delay.queue.demo.deadletter.queuea";
    public static final String DEAD_LETTER_QUEUEB_NAME = "delay.queue.demo.deadletter.queueb";


    public static final String DELAY_QUEUEC_NAME = "delay.queue.demo.business.queuec";
    public static final String DELAY_QUEUEC_ROUTING_KEY = "delay.queue.demo.business.queuec.routingkey";
    public static final String DEAD_LETTER_QUEUEC_ROUTING_KEY = "delay.queue.demo.deadletter.delay_anytime.routingkey";
    public static final String DEAD_LETTER_QUEUEC_NAME = "delay.queue.demo.deadletter.queuec";


    //声明延时exchange
    @Bean("delayExchange")
    public DirectExchange delayExchange(){
        return new DirectExchange(DELAY_EXCHANGE_NAME);
    }

    //声明死信exchange
    @Bean("deadLetterExchange")
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE);
    }

    // 声明延时队列A 延时10s
    // 并绑定到对应的死信交换机
    @Bean("delayQueueA")
    public Queue delayQueueA(){
        Map<String,Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
        // x-message-ttl  声明队列的TTL
        args.put("x-message-ttl", 1000);
        return QueueBuilder.durable(DELAY_QUEUEA_NAME).withArguments(args).build();
    }
    // 声明延时队列B 延时 60s
    // 并绑定到对应的死信交换机
    @Bean("delayQueueB")
    public Queue delayQueueB(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEB_ROUTING_KEY);
        // x-message-ttl  声明队列的TTL
        args.put("x-message-ttl", 60000);
        return QueueBuilder.durable(DELAY_QUEUEB_NAME).withArguments(args).build();
    }


    // 声明延时队列C 不设置过期时间
    // 并绑定到对应的死信交换机
    @Bean("delayQueueC")
    public Queue delayQueueC(){
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange    这里声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
        // x-dead-letter-routing-key  这里声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEC_ROUTING_KEY);

        return QueueBuilder.durable(DELAY_QUEUEC_NAME).withArguments(args).build();
    }

    // 声明死信队列A 用于接收延时10s处理的消息
    @Bean("deadLetterQueueA")
    public Queue deadLetterQueueA(){
        return new Queue(DEAD_LETTER_QUEUEA_NAME);
    }
    // 声明死信队列B 用于接收延时60s处理的消息
    @Bean("deadLetterQueueB")
    public Queue deadLetterQueueB(){
        return new Queue(DEAD_LETTER_QUEUEB_NAME);
    }

    // 声明死信队列C 用于接收延时任意时长处理的消息
    @Bean("deadLetterQueueC")
    public Queue deadLetterQueueC(){
        return new Queue(DEAD_LETTER_QUEUEC_NAME);
    }

    // 声明延时队列A绑定关系
    @Bean
    public Binding delayBindingA(@Qualifier("delayQueueA") Queue queue,
                                 @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEA_ROUTING_KEY);
    }
    // 声明业务队列B绑定关系
    @Bean
    public Binding delayBindingB(@Qualifier("delayQueueB") Queue queue,
                                 @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEB_ROUTING_KEY);
    }

    // 声明延时列C绑定关系
    @Bean
    public Binding delayBindingC(@Qualifier("delayQueueC") Queue queue,
                                 @Qualifier("delayExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DELAY_QUEUEC_ROUTING_KEY);
    }
    // 声明死信队列A绑定关系
    @Bean
    public Binding deadLetterBindingA(@Qualifier("deadLetterQueueA") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
            return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEA_ROUTING_KEY);
    }

    // 声明死信队列B绑定关系
    @Bean
    public Binding deadLetterBindingB(@Qualifier("deadLetterQueueB") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEB_ROUTING_KEY);
    }
    // 声明死信队列C绑定关系
    @Bean
    public Binding deadLetterBindingC(@Qualifier("deadLetterQueueC") Queue queue,
                                      @Qualifier("deadLetterExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(DEAD_LETTER_QUEUEC_ROUTING_KEY);
    }

}

上述代码声明了交换机和队列,并绑定了交换机和队列之间的路由关系。其中队列A和队列B是声明队列的时候就把过期时间设置好了,消息到达后规定时间之后就会进入死信队列。队列C没有声明消息过期时间,再发送消息的时候自动设置。
创建监听消费者类

@Slf4j
@Component
public class DeadLetterQueueConsumer {


    // DEAD_LETTER_QUEUEA_NAME
    @RabbitListener(queues = "delay.queue.demo.deadletter.queuea")
    public void receiveA(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列A收到消息:{}", new Date().toString(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }


    //DEAD_LETTER_QUEUEB_NAME
    @RabbitListener(queues ="delay.queue.demo.deadletter.queueb" )
    public void receiveB(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列B收到消息:{}", new Date().toString(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

    //对死信队列c进行消费

    @RabbitListener(queues ="delay.queue.demo.deadletter.queuec" )
    public void receiveC(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        log.info("当前时间:{},死信队列B收到消息:{}", new Date().toString(), msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

对三个死信队列进行监听,queues是队列的名称。
新建发送消息的类:

@Component
public class DelayMessageSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMsg(String msg, DelayTypeEnum type){
        switch (type){
            case DELAY_10s:
                //DELAY_EXCHANGE_NAME
                //
                rabbitTemplate.convertAndSend("delay.queue.demo.business.exchange","delay.queue.demo.business.queuea.routingkey",msg);
                break;
            case DELAY_60s:
                rabbitTemplate.convertAndSend("delay.queue.demo.business.exchange", "delay.queue.demo.business.queueb.routingkey", msg);
                break;
        };

    }

    //自行设置过期时间
    public void sendFreeMsg(String msg,String time){
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setExpiration(time);
        Message message = new Message(msg.getBytes(),messageProperties);
        rabbitTemplate.convertAndSend("delay.queue.demo.business.exchange", "delay.queue.demo.business.queuec.routingkey", message);
    }

}

第一种传递的是已经设置好队列时间的信息,第二种传递的是自行设置时间的消息信息;
新建controller来发送消息

@Slf4j
@RequestMapping("rabbitmq")
@RestController
public class RabbitMQMsgController {

    @Autowired
    DelayMessageSender sender;

    @RequestMapping("sendmsg")
    public void sendMsg(String msg, Integer delayType){
        log.info("当前时间:{},收到请求,msg:{},delayType:{}", new Date(), msg, delayType);
        sender.sendMsg(msg, Objects.requireNonNull(DelayTypeEnum.getDelayTypeEnumByValue(delayType)));
    }

    @RequestMapping("sendmsgfree")
    public void sendmsgfree(String msg, String time){
        log.info("当前时间:{},收到请求,msg:{},delayType:{}", new Date(), msg, time);
        sender.sendFreeMsg(msg, time);
    }
}

启动项目,可以再rabbit控制台看到我们创建的队列和交换机

centos安装Rabbitmq延迟插件 rabbitmq延迟队列原理_使用场景_02


centos安装Rabbitmq延迟插件 rabbitmq延迟队列原理_发送消息_03

启动项目访问路径进行测试:

localhost:6001/rabbitmq/sendmsg?msg=testMsg1&delayType=2

centos安装Rabbitmq延迟插件 rabbitmq延迟队列原理_使用场景_04

测试成功