【RabbitMQ 笔记】— 延时队列

通过上篇《【RabbitMQ 笔记】— 死信队列》的了解,我们知道队列中的消息过期后会被 RabbitMQ 转发到 DLX(前提是要为队列添加 DLX),进而路由到死信队列。而正好可以利用这个功能(DLX + TTL)来实现延时队列,废话不多说,先看图
image

图中为每个过期时间设置了单独的队列,比如 queue_5s 这个队列中的消息 TTL 都是 5s,消息过期后会进入 queue_delay_5s 这个死信队列。消费者直接订阅死信队列即可。那延时队列有哪些用途呢,常见的应用场景如下:

  • 下单之后如果三十分钟之内没有付款就自动取消订单
  • 订餐通知:下单成功后60s之后给用户发送短信通知
  • 新创建店铺,N天内没有上传商品,系统如何知道该信息,并发送激活短信
  • ......

示例代码如下

/**
 * 延时队列
 *
 * @author LBG - 2019/4/23
 */
public class DelayQueue {
    private static final String NORMAL_EXCHANGE = "normalDelayExchange";
    private static final String NORMAL_QUEUE_PREFIX = "normalQueue_";

    private static final String DELAY_EXCHANGE_PREFIX = "delay_exchange_";
    private static final String DELAY_QUEUE_PREFIX = "delay_queue_";
    private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2);

    public static void main(String[] args) throws InterruptedException {
        // 创建死信队列执行一次就好
        //preCreateDelayQueue();

        EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                new Consumer().consumeMsg();
            }
        });
        EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                new Producer().publishMsg();
            }
        });
    }

    /**
     * 提前将 dlx 和 死信队列创建好
     */
    private static void preCreateDelayQueue() {
        try(Connection connection = ConnectionUtil.getConnection()) {
            Channel channel = connection.createChannel();
            // 创建 normal exchange
            channel.exchangeDeclare(NORMAL_EXCHANGE, "direct", true, false, null);
            // 创建 normal queue,并分别设置队列消息的过期时间
            for (int i = 5; i <= 15; i+=5) {
                // 创建死信队列
                String delayExchangeName = DELAY_EXCHANGE_PREFIX + i;
                String delayQueueName = DELAY_QUEUE_PREFIX + i;
                channel.exchangeDeclare(delayExchangeName, "fanout", true, false, null);
                channel.queueDeclare(delayQueueName, true, false, false, null);
                channel.queueBind(delayQueueName, delayExchangeName, "");

                // 创建 normal queue, 并分别设置队列消息的过期时间及添加 DLX
                Map<String, Object> args = new HashMap<>(2);
                args.put("x-dead-letter-exchange", delayExchangeName);
                args.put("x-message-ttl", i * 1000);
                String normalQueueName = NORMAL_QUEUE_PREFIX + i;
                channel.queueDeclare(normalQueueName, true, false, false, args);
                channel.queueBind(normalQueueName, NORMAL_EXCHANGE, i + "s");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 生产者
    static class Producer {
        public void publishMsg() {
            try(Connection connection = ConnectionUtil.getConnection()) {
                Channel channel = connection.createChannel();
                for (int i = 5; i <= 15; i+=5) {
                    // 发送消息
                    byte[] bytes = ("delay message " + i).getBytes();
                    String routingKey = i + "s";
                    channel.basicPublish(NORMAL_EXCHANGE, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, bytes);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 消费者
    static class Consumer {
        public void consumeMsg() {
            try (Connection connection = ConnectionUtil.getConnection()) {
                final Channel channel = connection.createChannel();
                channel.basicQos(64);
                DefaultConsumer consumer = new DefaultConsumer(channel) {
                    @Override
                    public void handleDelivery(String consumerTag,
                                               Envelope envelope,
                                               AMQP.BasicProperties properties,
                                               byte[] body) throws IOException {
                        System.out.println("delay message: " + new String(body));
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        channel.basicAck(envelope.getDeliveryTag(), false);
                    }
                };
                for (int i = 5; i <= 15; i+=5) {
                    channel.basicConsume(DELAY_QUEUE_PREFIX + i, false, consumer);
                }
                while (true);
                //channel.close();
                } catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
}
posted @ 2022-06-09 13:32  Tailife  阅读(42)  评论(0编辑  收藏  举报