RabbitMQ如何实现延迟队列?

本文介绍如何利用RabbitMQ实现延迟队列,通过设置消息TTL和配置DLX来确保消息能在指定时间后被消费。适用于订单超时处理等场景。

欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


欢迎跳转到本文的原文链接:https://honeypps.com/mq/rabbitmq-how-to-make-delay-queue/

什么是延迟队列

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

场景一:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行一场处理。这是就可以使用延迟队列将订单信息发送到延迟队列。

场景二:用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将用户指令发送到延迟队列,当指令设定的时间到了再将指令推送到只能设备。


RabbitMQ怎么实现延迟队列

AMQP协议,以及RabbitMQ本身没有直接支持延迟队列的功能,但是可以通过TTL和DLX模拟出延迟队列的功能。

TTL(Time To Live)
RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter
RabbitMQ针对队列中的消息过期时间有两种方法可以设置。

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

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

详细可以参考:RabbitMQ之TTL(Time-To-Live 过期时间)

DLX (Dead-Letter-Exchange)

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:指定routing-key发送

队列出现dead letter的情况有:

  • 消息或者队列的TTL过期
  • 队列达到最大长度
  • 消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false

利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。

详细可以参考: RabbitMQ之死信队列


代码示例

首先建立2个exchange和2个queue:

  • exchange_delay_begin:这个是producer端发送时调用的exchange, 将消息发送至queue_dealy_begin中。
  • queue_delay_begin: 通过routingKey="delay"绑定exchang_delay_begin, 同时配置DLX=exchange_delay_done, 当消息变成死信时,发往exchange_delay_done中。
  • exchange_delay_done: 死信的exchange, 如果不配置x-dead-letter-routing-key则采用原有默认的routingKey,即queue_delay_begin绑定exchang_delay_beghin采用的“delay”。
  • queue_delay_done:消息在TTL到期之后,最终通过exchang_delay_done发送值此queue,消费端通过消费此queue的消息,即可以达到延迟的效果。

1. 建立exchange和queue的代码(当然这里可以通过RabbitMQ的管理界面来实现,无需code相关代码):

channel.exchangeDeclare("exchange_delay_begin", "direct", true);
channel.exchangeDeclare("exchange_delay_done", "direct", true);

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "exchange_delay_done");
channel.queueDeclare("queue_delay_begin", true, false, false, args);
channel.queueDeclare("queue_delay_done", true, false, false, null);

channel.queueBind("queue_delay_begin", "exchange_delay_begin", "delay");
channel.queueBind("queue_delay_done", "exchange_delay_done", "delay");

2. consumer端代码:

QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume("queue_delay_done", false, consumer);

while (true) {
    QueueingConsumer.Delivery delivery = consumer.nextDelivery();
    String msg = new String(delivery.getBody());
    System.out.println("receive msg time:" + new Date() + ", msg body:" + msg);
    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}

3. producer端代码:设置消息的延迟时间为1min。

AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
builder.expiration("60000");//设置消息TTL
builder.deliveryMode(2);//设置消息持久化
AMQP.BasicProperties properties = builder.build();

String message = String.valueOf(new Date());
channel.basicPublish("exchange_delay_begin","delay",properties,message.getBytes());

在创建完exchange和queue之后,首先执行consumer端的代码,之后执行producer端的代码,待producer发送完毕之后,查看consumer端的输出:

receive msg time:Tue Feb 14 21:06:19 CST 2017, msg body:Tue Feb 14 21:05:19 CST 2017

可以看到延迟1min消费了相关消息。大功告成~

欲了解更多消息中间件的内容,可以关注:消息中间件收录集


参考资料

  1. rabbitmq 实现延迟队列的两种方式
  2. RabbitMQ之TTL(Time-To-Live 过期时间)
  3. RabbitMQ之死信队列

欢迎跳转到本文的原文链接:https://honeypps.com/mq/rabbitmq-how-to-make-delay-queue/


欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


RabbitMQ本身未提供延迟队列功能,但可通过以下方式实现延迟队列: ### TTL + 死信队列 TTL(Time To Live)即消息的存活时间,死信队列是当消息成为死信后被重新发送到的队列。先将消息发到指定了TTL时长的队列A中,队列A没有消费者,队列A中的消息会过期,等消息过期后,就会加入到队列B(死信队列),B队列有消费者监听,收到消息后进行后续逻辑处理,从而达到延迟效果。不过这种实现方式只能为队列设置消息延迟的时长,不能为每个消息指定延迟时长,粒度比较粗,使用时需注意业务场景[^2][^4]。 示例代码(Java): ```java import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeoutException; public class DelayedQueueWithTTLAndDLX { private static final String QUEUE_A = "queue_A"; private static final String QUEUE_B = "queue_B"; private static final String EXCHANGE_DLX = "exchange_dlx"; private static final int TTL = 5000; // 5 seconds public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 设置队列A的参数,指定死信交换机 Map<String, Object> argsMap = new HashMap<>(); argsMap.put("x-dead-letter-exchange", EXCHANGE_DLX); argsMap.put("x-message-ttl", TTL); // 声明队列A channel.queueDeclare(QUEUE_A, false, false, false, argsMap); // 声明死信交换机 channel.exchangeDeclare(EXCHANGE_DLX, "direct"); // 声明队列B channel.queueDeclare(QUEUE_B, false, false, false, null); // 绑定队列B到死信交换机 channel.queueBind(QUEUE_B, EXCHANGE_DLX, ""); // 发送消息到队列A String message = "Delayed message"; channel.basicPublish("", QUEUE_A, null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close(); } } ``` ### 在消息属性上设置TTL 将TTL设置在消息属性里,能更灵活地实现延迟队列的具体业务开发。需要增加一个延时队列,用于接收设置为任意延时时长的消息,同时增加一个相应的死信队列和routing key。不过该方法有弊端,RabbitMQ只会检查第一个消息是否过期,如果过期则丢到死信队列,所以如果第一个消息的延时时长很长,而第二个消息的延时时长很短,则第二个消息并不会优先得到执行[^3]。 ### 使用rabbitmq - delayed - message - exchange插件 为了解决上述在消息属性上设置TTL的问题,可以利用rabbitmq - delayed - message - exchange插件。该插件允许在发送消息时为每条消息单独设置延迟时间。使用时,需要先安装该插件,然后声明一个类型为`x-delayed-message`的交换机,发送消息时在消息的headers中设置`x-delay`属性来指定延迟时间(单位为毫秒)[^3]。 示例代码(Java): ```java import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeoutException; public class DelayedQueueWithPlugin { private static final String EXCHANGE_DELAYED = "exchange_delayed"; private static final String QUEUE_DELAYED = "queue_delayed"; private static final int DELAY_TIME = 5000; // 5 seconds public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); // 声明延迟交换机 Map<String, Object> argsMap = new HashMap<>(); argsMap.put("x-delayed-type", "direct"); channel.exchangeDeclare(EXCHANGE_DELAYED, "x-delayed-message", true, false, argsMap); // 声明队列 channel.queueDeclare(QUEUE_DELAYED, false, false, false, null); // 绑定队列到延迟交换机 channel.queueBind(QUEUE_DELAYED, EXCHANGE_DELAYED, ""); // 设置消息的延迟属性 Map<String, Object> headers = new HashMap<>(); headers.put("x-delay", DELAY_TIME); AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() .headers(headers) .build(); // 发送延迟消息 String message = "Delayed message with plugin"; channel.basicPublish(EXCHANGE_DELAYED, "", properties, message.getBytes()); System.out.println(" [x] Sent '" + message + "' with delay of " + DELAY_TIME + " ms"); channel.close(); connection.close(); } } ```
评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值