实现Rabbitmq的延时队列,只需要这几步

延迟队列

延迟队列存储的对象是延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息进行消费,而是等待指定时间后,消费者才拿到这个消息进行消费。

应用场景

比如未付款订单,超过一定时间后,系统自动取消订单并释放占有物品。


实现方式

使用RabbitMQ实现延迟队列有以下两种方式,本篇文章主要介绍第一种,第二种在下一篇文章介绍

1、TTL + DLX

2、使用延迟插件


TTL + DLX

Time To Live(TTL)

RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

RabbitMQ针对队列中的消息过期时间有两种方法可以设置。

A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。

如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter


DLX(Dead-Letter-Exchange)

DLX(Dead-Letter-Exchange)交换机,当信息在一个队列变成死信(Dead message)后,能被重新发送到DLX中,绑定DLX的队列称之为死信队列。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题。

RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。

x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange

x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

队列出现dead letter的情况有:

1、消息或者队列的TTL过期

2、队列达到最大长度

3、消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false

设置了TTL规则之后当消息在一个队列中变成死信时,因为DLX的特性,它会被重转发到另一个Exchange或者Routing Key,这时候消息就可以重新被消费了。


实例

下面我就用TTL + DLX来实现Rabbitmq的延时队列

DLX的配置类

@Configuration
public class RabbitMqConfig {

    public static final String QUEUE_ORDER_CANCEL="queue_cancel";

    public static final String EXCHANGE_ORDER_CANCEL="exchange_cancel";

    public static final String QUEUE_TTL_ORDER_CANCEL="ttl_queue_cancel";

    public static final String EXCHANGE_TTL_ORDER_CANCEL="ttl_exchange_cancel";


    /**
     * 订单消息超时后所绑定的交换机
     */
    @Bean
    DirectExchange orderDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(EXCHANGE_ORDER_CANCEL)
                .durable(true)
                .build();
    }

    /**
     * 订单延迟队列队列所绑定的交换机
     */
    @Bean
    DirectExchange orderTtlDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(EXCHANGE_TTL_ORDER_CANCEL)
                .durable(true)
                .build();
    }

    /**
     * 超时订单实际消费队列
     */
    @Bean
    public Queue orderQueue() {
        return new Queue(QUEUE_ORDER_CANCEL);
    }

    /**
     * 订单延迟队列(死信队列)
     */
    @Bean
    public Queue orderTtlQueue() {
        return QueueBuilder
                .durable(QUEUE_TTL_ORDER_CANCEL)
                .withArgument("x-dead-letter-exchange", EXCHANGE_ORDER_CANCEL)//到期后转发的交换机
                .withArgument("x-dead-letter-routing-key", QUEUE_ORDER_CANCEL)//到期后转发的路由键
                .build();
    }

    /**
     * 将超时订单消费队列绑定到超时交换机
     */
    @Bean
    Binding orderBinding(){
        return BindingBuilder
                .bind(orderQueue())
                .to(orderDirect())
                .with(QUEUE_ORDER_CANCEL);
    }

    /**
     * 将订单延迟队列绑定到延迟交换机
     */
    @Bean
    Binding orderTtlBinding(DirectExchange orderTtlDirect,Queue orderTtlQueue){
        return BindingBuilder
                .bind(orderTtlQueue())
                .to(orderTtlDirect())
                .with(QUEUE_TTL_ORDER_CANCEL);
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){

        RabbitTemplate rabbitTemplate =new RabbitTemplate(connectionFactory);

        return rabbitTemplate;

    }


}

消息生产者,通过MessagePostProcessor在消息上设置过期时间

import org.springframework.amqp.core.Message;

@Component
public class RabbitmqSender {

    private static Logger LOGGER = LoggerFactory.getLogger(RabbitmqSender.class);

    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendMessage(Long orderId,final long delayTimes){
        //给延迟队列发送消息
        amqpTemplate.convertAndSend(EXCHANGE_TTL_ORDER_CANCEL, QUEUE_TTL_ORDER_CANCEL, orderId, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //给消息设置延迟毫秒值
                message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
                return message;
            }
        });
        LOGGER.info("send delay message orderId:{}",orderId);
    }
}

如果是Message 对象,使用以下方法设置过期时间

Message message=new Message("hello".getBytes(),new MessageProperties());
message.getMessageProperties().setExpiration(String.valueOf(1000));

以上是对单个消息进行设置过期时间,RabbitMQ支持为每个队列设置消息的超时时间,从消息入队列开始计算,只要超过了队列的超时时间配置,那么消息会自动过期。

设置队列消息过期时间的属性是:x-message-ttl

注意:设置了队列消息过期时间,那么队列里所有message的失效时间都是一样的,适用于消息失效时间一样的情况。如果想让消息的失效时间不一样,那么单独设置消息过期时间(如果队列消息过期时间和单独消息过期时间都存在,那么失效时间较小的先执行)

    /**
     * 订单延迟队列(死信队列)
     */
    @Bean
    public Queue orderTtlQueue() {
        return QueueBuilder
                .durable(QUEUE_TTL_ORDER_CANCEL)
                .withArgument("x-dead-letter-exchange", EXCHANGE_ORDER_CANCEL)//到期后转发的交换机
                .withArgument("x-dead-letter-routing-key", QUEUE_ORDER_CANCEL)//到期后转发的路由键
                .withArgument("x-message-ttl", 10000)//设置队列消息过期时间,毫秒值
                .build();
    }

消费消息跟普通消息一样,不需要其他改动

你可能感兴趣的:(MQ)