【RabbitMQ 笔记】— 延时队列
通过上篇《【RabbitMQ 笔记】— 死信队列》的了解,我们知道队列中的消息过期后会被 RabbitMQ 转发到 DLX(前提是要为队列添加 DLX),进而路由到死信队列。而正好可以利用这个功能(DLX + TTL)来实现延时队列,废话不多说,先看图
图中为每个过期时间设置了单独的队列,比如 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();
}
}
}
}