RabbitMQ实现延时队列

延时队列的概念

场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时。

场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单。

上述场景都是有一个共同的地方,在物联网在给终端下发命令时,在用户订单下单时,会发送一个消息到队列钟,同时设定改条消息的过期时间,如果终端在过期时间之内响应了物联网的命令或者用户在过期时间之内进行了付款,则会将改条消息删除,否则改条消息机会被消费,告知物联网超时或者用户未在规定时间内付款,自动取消订单。

使用RabbitMQ来实现延迟任务必须先了解RabbitMQ的两个概念:消息的TTL死信Exchange,通过这两者的组合来实现上述需求。

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

可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串

当消息扔到队列中后,过了60秒,如果没有被消费,它就死了。不会被消费者消费到。这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去。单靠死信还不能实现延迟任务,还要靠Dead Letter Exchange

Dead Letter Exchanges

Exchage的概念在这里就不在赘述,可以从这里进行了解。一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。

1. 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。

2. 上面的消息的TTL到了,消息过期了。

3. 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。

Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

接下来是demo实现:

package com.ucar.rabbitmq.config;

import com.rabbitmq.client.AMQP;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 
 * @since 2018/8/27 16:42
 */
@Configuration
public class MyRabbitMQConfig {
 
    //延时队列
    public static final String delayQueue = "delayQueue";

    //正常队列
    public static final String normalQueue = "normalQueue";

    //延时交换机
    public static final String delayExchange = "delayExchange";

    //正常交换机
    public static final String normalExchange = "normalExchange";

    //延时队列路由键
    public static final String DELAY_ROUTING_KEY = "delay-routing-key";

    //正常队列路由键
    public static final String NORMAL_ROUTING_KEY = "normal-routing-key";

    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public Queue getDelayQueue() {
        Map args = new HashMap();
        /**
         * 消息发送给延时队列
         * 设置延时队列的过期时间为5秒钟
         * 5秒之后,延时队列将消息发送给普通队列
         */
        args.put("x-dead-letter-exchange", normalExchange);
        args.put("x-dead-letter-routing-key", NORMAL_ROUTING_KEY);
        args.put("x-message-ttl", 5000);
        return QueueBuilder.durable(delayQueue).withArguments(args).build();
    }

    //创建延时交换机
    @Bean
    public Exchange getDelayExchange() {
        return ExchangeBuilder.directExchange(delayExchange).durable(true).build();
    }

    //延时与延时交换机进行绑定
    @Bean
    public Binding bindDelay() {
        return BindingBuilder.bind(getDelayQueue()).to(getDelayExchange()).with(DELAY_ROUTING_KEY).noargs();
    }

    //创建延时交换机
    @Bean
    public Queue getNormalQueue() {
        return new Queue(normalQueue);
    }

    //创建普通交换机
    @Bean
    public Exchange getNormalExchange() {
        return ExchangeBuilder.directExchange(normalExchange).durable(true).build();
    }

    //普通队列与普通交换机进行绑定
    @Bean
    public Binding bindNormal() {
        return BindingBuilder.bind(getNormalQueue()).to(getNormalExchange()).with(NORMAL_ROUTING_KEY).noargs();
    }
}
@PostMapping("/test00")
    public void sendDelayMessage() {
        logger.info("开始发送消息: " + "延时5秒消费");
        rabbitTemplate.convertAndSend(MyRabbitMQConfig.delayExchange, MyRabbitMQConfig.DELAY_ROUTING_KEY, "延时5秒消费");
    }
spring:
  rabbitmq:
    virtual-host: /
    host: localhost
    username: guest
    password: guest

你可能感兴趣的:(消息队列,MQ)