rabbitmq如何实现延时队列

本文介绍了如何在RabbitMQ中实现延迟队列,通过两种方式:一是利用TTL(TimeToLive)和死信队列(DLX)的组合,二是使用RabbitMQ官方延迟插件。详细阐述了两者的工作原理和代码实现,包括消息过期、死信转发和消费者的处理逻辑。并给出了具体的Java代码示例和配置,适用于下单后30分钟未支付自动取消订单的业务场景。

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

一、楔子

延时队列在许多业务场景中都有着广泛的运用。但可惜的是在RabbitMQ中并未提供延迟队列功能。这里小七结合工作所用,列出2种实现方式。

(1)使用TTL+死信队列组合实现延迟队列的效果。

(2)使用RabbitMQ官方延迟插件,实现延时队列效果。

二、使用TTL+死信队列组合实现延迟队列的效果

使用这种方式实现延时队列,我们首先要理清楚2个概念。TTL和死信队列。

1、TTL

TTL 全称 Time To Live(存活时间/过期时间)。当消息到达存活时间后,还没有被消费,会被自动清除。RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。可以类比redis的TTL。

2、死信队列

死信队列,英文缩写:DLX  。Dead Letter Exchange(死信交换机)。那如何让一个普通的队列成为死信队列呢?

消息成为死信的三种情况:

(1) 队列消息长度到达限制;

(2)消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;

(3) 原队列存在消息过期设置,消息到达超时时间未被消费;

3、逻辑整理

结合TTL和死信队列,我们可以做以下思考

(1)创建队列b(普通队列)和队列a(死信队列)分别绑定交换机d,并且提供监听队列b(普通队列)的消费者c,队列a(死信队列)不提供消费者。

(2)生产者发送包含TTL的消息到队列a(死信队列)。

(3)队列a(死信队列)过期后,经由交换机d转发到队列b(普通队列)。

(4)消费者c消费队列b(普通队列)的信息。

如图:

图片

4、代码实现

有一个经典的业务场景可以使用延迟队列:下单后,30分钟未支付,取消订单。这里小七基于这个需求给出代码实现。

测试消息体

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageStruct implements Serializable {
    private static final long serialVersionUID = 392365881428311040L;
    private String message;
}

RabbitMQ常量池

public interface RabbitConsts {
    //=====================TTL+死信队列实现======================//
    /**
     * 声明了队列里的死信转发到的DLX名称
     */
    String X_DEAD_LETTER_EXCHANGE = "x-dead-letter-exchange";
    /**
     * 声明了这些死信在转发时携带的 routing-key 名称
     */
    String X_DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";
    /**
     * 消息过期后会转到该队列中,需要消费者监听并消费
     */
    String CANCELORDER_CONSUME_QUEUE = "cancelOrderConsumeQueue";
    /**
     * 声明了生产者在发送消息时携带的 routing-key 名称
     */
    String CANCELORDER_SEND_ROUTINGKEY = "cancelOrderSendRoutingKey";
    /**
     * 声明了生产者在发送消息时携带的 Queue 名称
     */
    String CANCELORDER_QUEUE = "cancelOrderSendQueue";
}

配置文件

@Slf4j
@Configuration
public class RabbitMqConfig {
    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause));
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message));
        return rabbitTemplate;
    }
@Bean
public TopicExchange synCancelOrderDelayExchange(){
    TopicExchange topicExchange = new TopicExchange(RabbitConsts.X_DEAD_LETTER_EXCHANGE,true,false);
    return topicExchange;
}
@Bean
public Queue synCancelOrderDelaySendQueue() {
    /**
     durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
     auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
     exclusive  表示该消息队列是否只在当前connection生效,默认是false
     */
    Map<String, Object> params = new HashMap<>();
    // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
    params.put(RabbitConsts.X_DEAD_LETTER_EXCHANGE, RabbitConsts.X_DEAD_LETTER_EXCHANGE);
    // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
    params.put(RabbitConsts.X_DEAD_LETTER_ROUTING_KEY,RabbitConsts.X_DEAD_LETTER_ROUTING_KEY);
    return new Queue(RabbitConsts.CANCELORDER_QUEUE, true, false, false,params);
}
@Bean
public Queue synCancelOrderDelayConsumeQueue() {
    /**
     durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
     auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
     exclusive  表示该消息队列是否只在当前connection生效,默认是false
     死信队列里的消息过期后会转到该队列中
     */
    return new Queue(RabbitConsts.CANCELORDER_CONSUME_QUEUE, true, false, false);
}
@Bean
public Binding bindingCancelOrderSend() {
    return BindingBuilder.bind(synCancelOrderDelaySendQueue()).to(synCancelOrderDelayExchange()).with(RabbitConsts.CANCELORDER_SEND_ROUTINGKEY);
}
@Bean
public Binding bindingCancelOrderConsume() {
    return BindingBuilder.bind(synCancelOrderDelayConsumeQueue()).to(synCancelOrderDelayExchange()).with(RabbitConsts.X_DEAD_LETTER_ROUTING_KEY);
}
}

消费者

@Slf4j
@Component
@RabbitListener(queues = RabbitConsts.CANCELORDER_CONSUME_QUEUE)
public class CancelOrderConsumer {
    @RabbitHandler
    public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) {
        //  如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("订单自动取消延迟队列,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct));
            // 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

生产者

@RestController
@Api(tags = "测试SpringBoot整合进行各种工作模式信息的发送", value = "测试SpringBoot整合进行各种工作模式信息的发送")
public class ProducerController {
   @Autowired
   private RabbitTemplate rabbitTemplate;
   /**
    * 下单完成会发送消息到延迟队列 用于自动取消订单
    */
   @ApiOperation(value="测试延迟队列发送接口(订单取消)",notes="测试延迟队列发送(订单取消)")
   @GetMapping(value="/synSendCancelOrderMessage")
   public void synSendCancelOrderMessage(){
      rabbitTemplate.convertAndSend(RabbitConsts.X_DEAD_LETTER_EXCHANGE,
            RabbitConsts.CANCELORDER_SEND_ROUTINGKEY, new MessageStruct("自动取消订单"), message -> {
               message.getMessageProperties().setExpiration(String.valueOf(10 * 1000));
               return message;
            });
   }
}

完整项目代码地址:https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-rabbitmq

三、使用RabbitMQ官方延迟插件,实现延时队列效果。

基于TTL+死信队列的组合虽然可以实现延迟队列,但是对于平常的开发太过繁琐。于是小七在rabbitmq官方,发现了官方推荐的一款延迟队列插件,让延时队列的实现同一般队列那么简单。

测试消息体

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageStruct implements Serializable {
    private static final long serialVersionUID = 392365881428311040L;
    private String message;
}

RabbitMQ常量池

public interface RabbitConsts {
/**
 * 延迟队列
 */
String DELAY_QUEUE = "delay.queue";
/**
 * 延迟队列交换器
 */
String DELAY_MODE_QUEUE = "delay.mode";
}

配置文件

@Slf4j
@Configuration
public class RabbitMqConfig {
    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause));
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message));
        return rabbitTemplate;
    }
   //===================延迟队列插件实现start====================//
/**
 * 延迟队列
 */
@Bean
public Queue delayQueue() {
    return new Queue(RabbitConsts.DELAY_QUEUE, true);
}
/**
 * 延迟队列交换器, x-delayed-type 和 x-delayed-message 固定
 */
@Bean
public CustomExchange delayExchange() {
    Map<String, Object> args = new HashMap<>(16);
    args.put("x-delayed-type", "direct");
    return new CustomExchange(RabbitConsts.DELAY_MODE_QUEUE, "x-delayed-message", true, false, args);
}
/**
 * 延迟队列绑定自定义交换器
 *
 * @param delayQueue    队列
 * @param delayExchange 延迟交换器
 */
@Bean
public Binding delayBinding(Queue delayQueue, CustomExchange delayExchange) {
    return BindingBuilder.bind(delayQueue).to(delayExchange).with(RabbitConsts.DELAY_QUEUE).noargs();
}
//===================延迟队列插件实现end====================/
}

消费者

@Slf4j
@Component
@RabbitListener(queues = RabbitConsts.DELAY_QUEUE)
public class DelayQueueConsumer {
    @RabbitHandler
    public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) {
        //  如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("延迟队列,手动ACK,接收消息:{}", JSONUtil.toJsonStr(messageStruct));
            // 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

生产者

@RestController
@Api(tags = "测试SpringBoot整合进行各种工作模式信息的发送", value = "测试SpringBoot整合进行各种工作模式信息的发送")
public class ProducerController {
   @Autowired
   private RabbitTemplate rabbitTemplate;
   /**
 * 测试延迟队列发送
 */
@ApiOperation(value="测试延迟队列发送接口",notes="测试延迟队列发送")
@GetMapping(value="/sendDelay")
public void sendDelay() {
   rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message, delay 5s, " + DateUtil
         .date()), message -> {
      message.getMessageProperties().setHeader("x-delay", 5000);
      return message;
   });
   rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message,  delay 2s, " + DateUtil
         .date()), message -> {
      message.getMessageProperties().setHeader("x-delay", 2000);
      return message;
   });
   rabbitTemplate.convertAndSend(RabbitConsts.DELAY_MODE_QUEUE, RabbitConsts.DELAY_QUEUE, new MessageStruct("delay message,  delay 8s, " + DateUtil
         .date()), message -> {
      message.getMessageProperties().setHeader("x-delay", 8000);
      return message;
   });
}
}

完整项目代码地址:https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-rabbitmq

### 如何使用 RabbitMQ 实现延迟队列 #### 使用 RabbitMQ 的死信机制实现延迟队列 RabbitMQ 提供了一种通过 **死信交换器 (Dead Letter Exchange, DLX)** 和普通队列相结合的方式来实现延迟队列的功能。这种方法的核心在于利用消息的 TTL(Time To Live)属性,当消息超过设定的时间后会被自动丢弃并进入死信队列。 以下是具体的实现方法: 1. 配置两个队列:一个是带有 TTL 属性的消息队列,另一个是用于接收过期消息的死信队列。 2. 将死信队列绑定到一个死信交换器上,并配置相应的路由键。 3. 发布一条具有 TTL 的消息至初始队列,在消息到期后会自动被转移到死信队列中。 此过程的具体操作步骤已在参考资料中有提及[^2]。 #### 示例代码展示 下面是一个基于 Python 的 Pika 库实现 RabbitMQ 延迟队列的例子: ```python import pika import json from datetime import timedelta def publish_delayed_message(channel, exchange_name, routing_key, message_body, delay_ms): """ Publish a delayed message to the RabbitMQ queue. :param channel: The RabbitMQ channel object :param exchange_name: Name of the dead-letter exchange :param routing_key: Routing key for the dead-letter queue :param message_body: Message content as dictionary :param delay_ms: Delay time in milliseconds """ # Convert message body to JSON string message_json = json.dumps(message_body) # Set up properties with expiration and delivery mode props = pika.BasicProperties( expiration=str(delay_ms), # Expiration is set in milliseconds delivery_mode=2 # Persistent messages ) # Publish the message to the initial queue channel.basic_publish(exchange='', routing_key='delay_queue', properties=props, body=message_json) # Establish connection to RabbitMQ server connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # Declare queues channel.queue_declare(queue='delay_queue', arguments={ 'x-dead-letter-exchange': 'dl_exchange', 'x-dead-letter-routing-key': 'dead_letter_routing' }) channel.exchange_declare(exchange='dl_exchange', exchange_type='direct') channel.queue_declare(queue='dead_letter_queue') # Bind dead letter queue to exchange channel.queue_bind(exchange='dl_exchange', queue='dead_letter_queue', routing_key='dead_letter_routing') # Example usage message_content = {"order_id": "1001", "status": "pending"} publish_delayed_message(channel, '', 'dead_letter_routing', message_content, 5000) # 5 seconds delay print("Message published successfully.") # Close connection connection.close() ``` 上述代码展示了如何创建带延迟特性的队列以及发送延迟消息的过程。其中 `expiration` 参数定义了消息存活时间,单位为毫秒;一旦该时间段结束,未消费的消息即转入指定的死信队列。 对于更高级别的需求,还可以考虑启用官方插件 rabbitmq_delayed_message_exchange 来进一步优化延迟队列的行为[^4]。 #### 设置消息优先级 如果希望在延迟队列的基础上增加对不同重要程度事件的支持,则可通过设置队列的最大优先级参数 (`x-max-priority`) 并结合每条消息单独赋予其优先权值的方式达成目标[^5]。 --- ### 性能与可靠性考量 采用 RabbitMQ 构建延迟队列能够显著提升业务场景下的灵活性和稳定性,特别是在电商领域常见的订单超时取消等功能模块里表现尤为突出[^1][^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第七人格

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值