死信队列(DLX)
消费者在消费消息过程中出现意外导致消费失败即称为“死信”。而不希望这些执行失败的消息丢失,需要存储到另一个队列当中时,这种队列即为“死信队列”。
消息出现一下情况会导致 “死信”
-
消息被否定,消费者使用 channel.basicNack或channel.basicReject,并且requeue被设置为false时。
-
消息超过过期时间。消息在队列的存活时间超过了设置的生存时间(TTL)
-
消息数量超出队列长度(消息溢出)
以下将以“消息溢出”为案例
消费者
public class NormalConsumer {
// 死信队列名
private static final String DEAD_QUEUE_NAME = "dead-queue";
// 死信交换机
private static final String DEAD_EXCHANGE_NAME = "dead-exchange";
// 普通队列名称
private static final String NORMAL_QUEUE_NAME = "normal-queue";
// 普通交换机名称
private static final String NORMAL_EXCHANGE_NAME = "normal-exchange";
public static final String NORMAL_ROUTING_KEY = "normal-demo";
public static final String DEAD_ROUTING_KEY = "dead-demo";
public static void main(String[] args) throws Exception, TimeoutException {
Connection connection = RabbitConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明交换机
channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 声明队列
Map<String, Object> normalArguments = new HashMap<>();
// 死信队列绑定配置:死信交换机、死信routingKey、普通队列长度限制(超出长度变为死信)
normalArguments.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
normalArguments.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY);
normalArguments.put("x-max-length", 4);
channel.queueDeclare(NORMAL_QUEUE_NAME, false, false, false, normalArguments);
// 声明死信队列
Map<String, Object> deadArguments = new HashMap<>();
channel.queueDeclare(DEAD_QUEUE_NAME, false, false, false, deadArguments);
// 绑定普通交换机和普通队列
channel.queueBind(NORMAL_QUEUE_NAME, NORMAL_EXCHANGE_NAME, NORMAL_ROUTING_KEY);
// 绑定死信交换机和死信队列
channel.queueBind(DEAD_QUEUE_NAME, DEAD_EXCHANGE_NAME, DEAD_ROUTING_KEY);
System.out.println("normal consumer:waiting message...");
//消息消费成功之后的回调
DeliverCallback deliverCallback = (consumerTag, message) -> {
String messageStr = new String(message.getBody(), StandardCharsets.UTF_8);
if (messageStr.contains("5")) {
System.out.println("mock ==> 请手动停止NomalConsumer程序,重启消费者,模拟普通队列的消息超出长度,导致死信");
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("normal consumer:" + messageStr);
};
channel.basicConsume(NORMAL_QUEUE_NAME, true, deliverCallback, consumer -> {
});
}
}
死信消费者
public class DeadConsumer {
// 死信队列名
private static final String DEAD_QUEUE_NAME = "dead-queue";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnectionUtil.getConnection();
Channel channel = connection.createChannel();
System.out.println("dead consumer: waiting message...");
DeliverCallback deadCallback = (consumerTag, message) -> {
System.out.println("dead consumer: " + new String(message.getBody()));
};
channel.basicConsume(DEAD_QUEUE_NAME, true, deadCallback, consumerTag -> {
});
}
}
消息提供者
public class NormalAndDeadProducer {
// 普通交换机名称
private static final String NORMAL_EXCHANGE_NAME = "normal-exchange";
public static final String NORMAL_ROUTING_KEY = "normal-demo";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 死信消息TTL时间
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder()
.expiration("10000")
.build();
for (int i = 0; i < 10; i++) {
String message = "message info index is " + i;
channel.basicPublish(NORMAL_EXCHANGE_NAME, NORMAL_ROUTING_KEY, properties, message.getBytes(StandardCharsets.UTF_8));
}
}
}
请按顺序执行以下步骤:
- 启动 消费者(NormalConsumer)
- 启动 死信消费者(DeadConsumer)
- 启动 消息提供者(NormalAndDeadProducer)
- 此刻可以看到消费者消费到了一半,这时立即关闭消费者程序的运行
- 重新启动一次 消息提供者,造成队列消息溢出
- 查看 死信消费者 控制台,可以看到,有一部分消息进入了死信队列并被 死信消费者 消费了