RabbitMQ(五)之延迟消息

本文介绍了如何在RabbitMQ中使用死信交换机和延迟消息插件实现消息延迟功能,包括死信处理、插件安装步骤、创建交换机和队列,以及如何优化延迟订单检测过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

延迟消息:生产者发送消息时指定一个时间,消费者不会立刻收到消息,而是在指定时间之后才收到消息。
延迟任务:设置在一定时间之后才执行的任务

延迟消息的实现有两种:死信交换机,延迟消息插件。

一、死信交换机 

当一个队列中的消息满足下列情况之一时,就会成为死信 (dead letter):

1、消费者使用basic.reject或 basic.nack声明消费失败,并且消息的requeue参数设置为false。
2、消息是一个过期消息(达到了队列或消息本身设置的过期时间),超时无人消费。

3、要投递的队列消息堆积满了,最早的消息可能成为死信。

如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交换机称为死信交换机 (Dead Letter Exchange,简称DLX)。

 

原理:创建一个过期时间为30s的消息,投递到队列,由于simple.queue没有消费者,30会过期,就会投递到死信交换机,死信队列绑定的消费者30s后就会收到消息。

需要定义较多的死信交换机和队列,比较繁琐 ,适合用作一个兜底方案处理死信。

二、延迟消息插件(推荐使用)

RabbitMQ的官方也推出了一个插件,原生支持延迟消息功能。该插件的原理是设计了一种支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后再投递到队列。 

2.1、插件安装

1、点击进入https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases
下载与RabbitMQ版本对应的延迟消息插件


2、将下载的延迟消息插件存放在 D:\Program Files\RabbitMQ Server\rabbitmq_server-3.12.6\plugins 目录下

3、打开RabbitMQ Command Prompt (sbin dir)控制台,输入
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

 

4、安装成功后打开 RabbitMQ Management,看到一下场景即为安装成功

2.2、创建提供消息延迟功能的交换机

第一步,创建交换机和队列,以及消费者

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "delay.queue", durable = "true"),
            exchange = @Exchange(value = "delay.direct", delayed = "ture"),
            key = "hi"
    ))
    public void listenDelayQueue(String msg) {
        log.info("接收到delay.queue的消息:{}", msg);
    }

第二步,发送者发送消息

 

    @Test
    void testSendDelayMessage() {
        rabbitTemplate.convertAndSend("delay.direct", "hi", "hello", new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setDelay(10000);
                return message;
            }
        });
        log.info("消息发送成功!!");
    }

三、利用延迟消息实现取消超时订单 

 

设置30分钟后检测订单支付状态实现起来非常简单,但是存在两个问题:

1、如果并发较高,30分钟可能堆积消息过多,对MQ压力很大。
2、大多数订单在下单后1分钟内就会支付,但是却需要在MQ内等待30分钟,浪费资源。

解决思路:可以把30分钟的等待时间拆分成n个短的延迟时间,每隔一个时间检查一次,n个延迟时间刚好满足30分钟,如10s,10s,15s,15s,1m......

以下是优化的结果

第一步、定义消息,将消息封装为下面的格式

@Data
public class MultiDelayMessage<T> {
    /**
     * 消息体
     */
    private T data;
    /**
     * 记录延迟时间的集合
     */
    private List<Long> delayMillis;

    public MultiDelayMessage(T data, List<Long> delayMillis) {
        this.data = data;
        this.delayMillis = delayMillis;
    }

    public static <T> MultiDelayMessage<T> of(T data, Long... delayMillis) {
        return new MultiDelayMessage<>(data, CollUtils.newArrayList(delayMillis));
    }

    /**
     * 获取并移除下一个延迟时间
     *
     * @return 队列中的第一个延迟时间
     */
    public Long removeNextDelay() {
        return delayMillis.remove(0);
    }

    /**
     * 是否还有下一个延迟时间
     */
    public boolean hasNextDelay() {
        return !delayMillis.isEmpty();
    }
}

第二步、交换机和消息队列

public interface MqConstants {
    
    String DELAY_EXCHANGE = "trade.delay.topic";
    String DELAY_ORDER_QUEUE = "trade.order.delay.queue";
    String DELAY_ORDER_ROUTING_KEY = "order.query";
}

第三步,发送延迟消息

// 5.延迟检测订单状态消息
        try {
            MultiDelayMessage<Long> msg = MultiDelayMessage.of(order.getId(), 10000L, 10000L, 10000L, 15000L, 15000L, 30000L, 30000L);

            rabbitTemplate.convertAndSend(MqConstants.DELAY_EXCHANGE, MqConstants.DELAY_ORDER_ROUTING_KEY, msg, new MessagePostProcessor() {
                @Override
                public Message postProcessMessage(Message message) throws AmqpException {
                    message.getMessageProperties().setDelay(msg.removeNextDelay().intValue());
                    return message;
                }
            });
//            rabbitTemplate.convertAndSend(
//                    MqConstants.DELAY_EXCHANGE, MqConstants.DELAY_ORDER_ROUTING_KEY, msg,
//                    new DelayMessageProcessor(msg.removeNextDelay().intValue())
//            );
        } catch (AmqpException e) {
            log.error("延迟消息发送异常!", e);
        }

第四步、消费者监听消息

@Component
@RequiredArgsConstructor
public class OrderStatusCheckListener {

    private final IOrderService orderService;
    private final RabbitTemplate rabbitTemplate;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConstants.DELAY_ORDER_QUEUE, durable = "true"),
            exchange = @Exchange(value = MqConstants.DELAY_EXCHANGE, delayed = "true", type = ExchangeTypes.TOPIC),
            key = MqConstants.DELAY_ORDER_ROUTING_KEY
    ))
    public void listenOrderDelayMessage(MultiDelayMessage<Long> msg) {
        // 1.查询订单状态
        Order order = orderService.getById(msg.getData());
        // 2.判断是否已经支付
        if (order == null || order.getStatus() == 2) {
            // 订单不存在或者已经被处理
            return;
        }
        // TODO 3.去支付服务查询真正的支付状态
        boolean isPay = false; //模拟为支付服务未支付
        // 3.1.已支付,标记订单状态为已支付
        if (isPay) {
            orderService.markOrderPaySuccess(order.getId());
            return;
        }

        // 4.判断是否存在延迟时间
        if (msg.hasNextDelay()) {
            // 4.1.存在,重发延迟消息
            Long nextDelay = msg.removeNextDelay();
            rabbitTemplate.convertAndSend(
                    MqConstants.DELAY_EXCHANGE, MqConstants.DELAY_ORDER_ROUTING_KEY,
                    msg, new DelayMessageProcessor(nextDelay.intValue()));
            return;
        }
        // 5.不存在,取消订单
        orderService.cancelOrder(order.getId());
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值