【rabbitmq 高级特性】RabbitMQ 延迟队列全面解析

目录

[1. 延迟队列的概念与原理](#1. 延迟队列的概念与原理)

[1.1 什么是延迟队列](#1.1 什么是延迟队列)

[1.2 延迟队列的实现原理](#1.2 延迟队列的实现原理)

[2. TTL+死信队列实现延迟队列](#2. TTL+死信队列实现延迟队列)

[2.1 实现原理](#2.1 实现原理)

[2.2 代码实现](#2.2 代码实现)

[2.2.1 常量定义](#2.2.1 常量定义)

[2.2.2 配置类](#2.2.2 配置类)

[2.2.3 生产者](#2.2.3 生产者)

[2.2.4 消费者](#2.2.4 消费者)

[2.3 TTL+死信队列的局限性](#2.3 TTL+死信队列的局限性)

[3. 使用延迟插件实现延迟队列](#3. 使用延迟插件实现延迟队列)

[3.1 安装延迟插件](#3.1 安装延迟插件)

[3.2 代码实现](#3.2 代码实现)

[3.2.1 常量定义](#3.2.1 常量定义)

[3.2.2 配置类](#3.2.2 配置类)

[3.2.3 生产者](#3.2.3 生产者)

[3.2.4 消费者](#3.2.4 消费者)

[4. 两种实现方式的对比](#4. 两种实现方式的对比)

[5. 应用场景](#5. 应用场景)

[5.1 订单超时取消](#5.1 订单超时取消)

[5.2 定时提醒](#5.2 定时提醒)

[5.3 重试机制](#5.3 重试机制)

[6. 注意事项](#6. 注意事项)

[7. 管理界面查看](#7. 管理界面查看)


1. 延迟队列的概念与原理

1.1 什么是延迟队列

延迟队列(Delayed Queue)是指消息被发送后,不会立即被消费者获取,而是等待特定时间后,消费者才能获取到这些消息进行消费。

1.2 延迟队列的实现原理

RabbitMQ本身没有直接支持延迟队列的功能,但可以通过两种方式实现:

  1. TTL + 死信队列组合​:通过设置消息或队列的TTL,配合死信交换机实现

  2. 官方延迟插件 ​:使用RabbitMQ官方提供的rabbitmq_delayed_message_exchange插件

2. TTL+死信队列实现延迟队列

2.1 实现原理

通过设置消息或队列的TTL,使消息在指定时间后过期,然后通过死信机制将过期消息路由到死信队列,消费者监听死信队列即可实现延迟消费。

2.2 代码实现

2.2.1 常量定义
复制代码
public class Constant {
    // 死信交换机
    public static final String DLX_EXCHANGE_NAME = "dlx_exchange";
    // 死信队列
    public static final String DLX_QUEUE = "dlx_queue";
    // 正常交换机
    public static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
    // 正常队列
    public static final String NORMAL_QUEUE = "normal_queue";
}
2.2.2 配置类
复制代码
@Configuration
public class DLXConfig {
    // 死信交换机
    @Bean("dlxExchange")
    public Exchange dlxExchange() {
        return ExchangeBuilder.topicExchange(Constant.DLX_EXCHANGE_NAME)
                .durable(true)
                .build();
    }
    
    // 死信队列
    @Bean("dlxQueue")
    public Queue dlxQueue() {
        return QueueBuilder.durable(Constant.DLX_QUEUE).build();
    }
    
    // 死信绑定
    @Bean("dlxBinding")
    public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, 
                             @Qualifier("dlxQueue") Queue queue) {
        return BindingBuilder.bind(queue)
                .to(exchange)
                .with("dlx")
                .noargs();
    }
    
    // 正常交换机
    @Bean("normalExchange")
    public Exchange normalExchange() {
        return ExchangeBuilder.topicExchange(Constant.NORMAL_EXCHANGE_NAME)
                .durable(true)
                .build();
    }
    
    // 正常队列(绑定死信交换机)
    @Bean("normalQueue")
    public Queue normalQueue() {
        Map<String, Object> arguments = new HashMap<>();
        // 绑定死信交换机
        arguments.put("x-dead-letter-exchange", Constant.DLX_EXCHANGE_NAME);
        // 设置死信路由键
        arguments.put("x-dead-letter-routing-key", "dlx");
        
        return QueueBuilder.durable(Constant.NORMAL_QUEUE)
                .withArguments(arguments)
                .build();
    }
    
    // 正常绑定
    @Bean("normalBinding")
    public Binding normalBinding(@Qualifier("normalExchange") Exchange exchange,
                                @Qualifier("normalQueue") Queue queue) {
        return BindingBuilder.bind(queue)
                .to(exchange)
                .with("normal")
                .noargs();
    }
}
2.2.3 生产者
复制代码
@RestController
@RequestMapping("/producer")
public class DelayProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/delay")
    public String delay() {
        // 发送带TTL的消息
        rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE_NAME, "normal",
                "ttl test 10s..." + new Date(), messagePostProcessor -> {
                    messagePostProcessor.getMessageProperties().setExpiration("10000"); // 10s过期
                    return messagePostProcessor;
                });
                
        rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE_NAME, "normal",
                "ttl test 20s..." + new Date(), messagePostProcessor -> {
                    messagePostProcessor.getMessageProperties().setExpiration("20000"); // 20s过期
                    return messagePostProcessor;
                });
        
        return "发送成功!";
    }
}
2.2.4 消费者
复制代码
@Component
public class DelayConsumer {
    // 监听死信队列(即延迟队列)
    @RabbitListener(queues = Constant.DLX_QUEUE)
    public void listenerDLXQueue(Message message, Channel channel) throws Exception {
        System.out.printf("%tc 接收到延迟消息: %s%n", 
                         new Date(), 
                         new String(message.getBody(), "UTF-8"));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
    }
}

2.3 TTL+死信队列的局限性

这种方法存在一个问题:RabbitMQ只会检查队列头部的消息是否过期,如果头部消息的TTL很长,即使后面的消息TTL较短,也会被阻塞,直到头部消息过期。这会导致消息的实际延迟时间可能比预期的要长。

3. 使用延迟插件实现延迟队列

3.1 安装延迟插件

  1. 下载插件:从RabbitMQ官方插件页面下载对应版本的插件

  2. 安装插件:

    复制代码
    # 将插件文件放到RabbitMQ插件目录
    rabbitmq-plugins enable rabbitmq_delayed_message_exchange
    # 重启RabbitMQ服务
    service rabbitmq-server restart

3.2 代码实现

3.2.1 常量定义
复制代码
public class Constant {
    // 延迟交换机
    public static final String DELAYED_EXCHANGE_NAME = "delayed_exchange";
    // 延迟队列
    public static final String DELAYED_QUEUE = "delayed_queue";
}
3.2.2 配置类
复制代码
@Configuration
public class DelayedConfig {
    // 延迟交换机(注意类型是x-delayed-message)
    @Bean("delayedExchange")
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        
        return new CustomExchange(Constant.DELAYED_EXCHANGE_NAME, 
                                 "x-delayed-message", 
                                 true, 
                                 false, 
                                 args);
    }
    
    // 延迟队列
    @Bean("delayedQueue")
    public Queue delayedQueue() {
        return QueueBuilder.durable(Constant.DELAYED_QUEUE).build();
    }
    
    // 延迟绑定
    @Bean("delayedBinding")
    public Binding delayedBinding(@Qualifier("delayedExchange") CustomExchange exchange,
                                  @Qualifier("delayedQueue") Queue queue) {
        return BindingBuilder.bind(queue)
                .to(exchange)
                .with("delayed")
                .noargs();
    }
}
3.2.3 生产者
复制代码
@RestController
@RequestMapping("/producer")
public class DelayedProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/delay2")
    public String delay2() {
        // 发送延迟消息
        rabbitTemplate.convertAndSend(Constant.DELAYED_EXCHANGE_NAME, "delayed",
                "delayed test 20s..." + new Date(), messagePostProcessor -> {
                    messagePostProcessor.getMessageProperties().setDelay(20000); // 20s延迟
                    return messagePostProcessor;
                });
                
        rabbitTemplate.convertAndSend(Constant.DELAYED_EXCHANGE_NAME, "delayed",
                "delayed test 10s..." + new Date(), messagePostProcessor -> {
                    messagePostProcessor.getMessageProperties().setDelay(10000); // 10s延迟
                    return messagePostProcessor;
                });
        
        return "发送成功!";
    }
}
3.2.4 消费者
复制代码
@Component
public class DelayedConsumer {
    // 监听延迟队列
    @RabbitListener(queues = Constant.DELAYED_QUEUE)
    public void listenerDelayedQueue(Message message, Channel channel) throws Exception {
        System.out.printf("%tc 接收到延迟消息: %s%n", 
                         new Date(), 
                         new String(message.getBody(), "UTF-8"));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
    }
}

4. 两种实现方式的对比

特性 TTL+死信队列 延迟插件
消息顺序 可能乱序(头部阻塞问题) 保证按延迟时间顺序
安装要求 无需额外安装 需要安装插件
灵活性 较低 较高
性能 较好 较好
适用场景 简单延迟需求 复杂延迟需求

5. 应用场景

5.1 订单超时取消

复制代码
// 订单创建30分钟后未支付自动取消
public void createOrder(Order order) {
    // 创建订单逻辑...
    
    // 发送延迟消息
    rabbitTemplate.convertAndSend(Constant.DELAYED_EXCHANGE_NAME, "order.cancel",
            order.getId(), messagePostProcessor -> {
                messagePostProcessor.getMessageProperties().setDelay(30 * 60 * 1000); // 30分钟
                return messagePostProcessor;
            });
}

5.2 定时提醒

复制代码
// 会议开始前15分钟提醒参会人
public void scheduleMeeting(Meeting meeting) {
    // 安排会议逻辑...
    
    // 计算延迟时间
    long delay = meeting.getStartTime().getTime() - System.currentTimeMillis() - 15 * 60 * 1000;
    
    // 发送延迟消息
    rabbitTemplate.convertAndSend(Constant.DELAYED_EXCHANGE_NAME, "meeting.remind",
            meeting, messagePostProcessor -> {
                messagePostProcessor.getMessageProperties().setDelay((int) delay);
                return messagePostProcessor;
            });
}

5.3 重试机制

复制代码
// 失败操作延迟重试
public void handleFailedOperation(Operation operation) {
    // 计算重试延迟(指数退避)
    long delay = calculateRetryDelay(operation.getAttemptCount());
    
    // 发送延迟重试消息
    rabbitTemplate.convertAndSend(Constant.DELAYED_EXCHANGE_NAME, "operation.retry",
            operation, messagePostProcessor -> {
                messagePostProcessor.getMessageProperties().setDelay((int) delay);
                return messagePostProcessor;
            });
}

6. 注意事项

  1. 延迟精度​:延迟队列不保证精确的时间延迟,可能会有几毫秒的误差

  2. 内存使用​:大量延迟消息可能会占用较多内存,需要合理设置延迟时间

  3. 集群环境​:在RabbitMQ集群中,延迟插件需要在所有节点上安装和启用

  4. 消息持久化​:重要延迟消息应该设置为持久化,防止RabbitMQ重启导致消息丢失

  5. 监控告警​:需要监控延迟队列的积压情况,设置合理的告警机制

7. 管理界面查看

使用延迟插件后,在RabbitMQ管理界面可以看到特殊的交换机类型:

  • Type ​:x-delayed-message

  • Features ​:D(持久化)、DLX(延迟交换机)

通过延迟队列,RabbitMQ能够处理各种需要定时或延迟执行的任务,大大增强了其应用场景和灵活性。根据具体需求选择合适的实现方式,可以构建出高效可靠的延迟任务处理系统。

相关推荐
隐语SecretFlow21 小时前
【隐语Serectflow】基于隐私保护的分布式数字身份认证技术研究及实践探索
分布式
回家路上绕了弯21 小时前
支付请求幂等性设计:从原理到落地,杜绝重复扣款
分布式·后端
小马爱打代码1 天前
SpringBoot + Quartz + Redis:分布式任务调度系统 - 从架构设计到企业级落地
spring boot·redis·分布式
debug骑士1 天前
面向云原生微服务的Go高并发架构实践与性能优化工程化经验分享案例研究
rabbitmq
无心水1 天前
【分布式利器:限流】3、微服务分布式限流:Sentinel集群限流+Resilience4j使用教程
分布式·微服务·架构·sentinel·分布式限流·resilience4j·分布式利器
2501_941802481 天前
Java高性能微服务架构与Spring Boot实战分享:分布式服务设计、负载均衡与优化经验
rabbitmq
一起学开源1 天前
分布式基石:CAP定理与ACID的取舍艺术
分布式·微服务·架构·流程图·软件工程
雁于飞1 天前
分布式基础
java·spring boot·分布式·spring·wpf·cloud native
2501_941147421 天前
高并发日志系统ELK/Fluentd/ClickHouse在互联网优化实践经验分享
rabbitmq
Tadas-Gao1 天前
Spring Boot 4.0架构革新:构建更精简、更安全、更高效的Java应用
java·spring boot·分布式·微服务·云原生·架构·系统架构