Dead Letter Exchanges : Dead Letter Exchanges — RabbitMQ
1. Concept
DLX, the full name of Dead Letter Exchanges, can be called a dead letter exchange, and some people call it a dead letter mailbox. When a message becomes a dead message in a queue, it can be resent to another switch. This switch is DLX, and the queue bound to DLX is called a dead letter queue .
Application scenario: In order to ensure that the message data of the order business is not lost, the dead letter queue mechanism of RabbitMQ needs to be used. When an exception occurs in message consumption, the message will be put into the dead letter queue. Another example: After the user successfully places an order in the mall and clicks to pay, it will automatically become invalid if the payment is not made within the specified time.
2. The source of dead letters
Reasons why a message becomes a dead letter:
-
The consumer negatively acknowledges the message with
basic.reject
orbasic.nack
withrequeue
the parameter set to false. (message rejected) -
Message TTL expired. (message expired)
-
A message was dropped because its queue reached its maximum length (queue was full)
3. Dead letter combat
1) Code Architecture Diagram
The producer only cares which exchange I send the message to, not which queue you forward it to, the queue is bound in the consumer statement
The producer is only responsible for producing messages and sending them to the switch, and any failures in the rest of the messages are caused by consumers
The dead letter queue is actually an ordinary queue bound to the dead letter switch, and the dead letter switch is just an ordinary switch, but it is a switch specially used to process dead letters
Consumer 01 wants to declare
normal_exchange
anddead_exchange
andnormal_queue
and anddead_queue
pairs of columns
"queue" – 队列的名称
"durable"——如果我们声明一个持久队列,则为真(该队列将在服务器重启后继续存在)
"exclusive" – 如果我们声明一个独占队列,则为 true(仅限于此连接)
"autoDelete" – 如果我们声明一个自动删除队列,则为 true(服务器将在不再使用时将其删除)
"arguments" – 队列的其他属性(构造参数)
Queue.DeclareOk queueDeclare(String queue,
boolean durable,
boolean exclusive,
boolean autoDelete,
Map<String, Object> arguments) throws IOException;
2) Consumer 01 Code
/**
* @desc
* @auth llp
* @date 2022年08月04日 22:48
*/
public class Consumer01 {
/** 普通交换机名称 */
private static final String NORMAL_EXCHANGE = "normal_exchange";
/** 普通队列名称 */
private static final String NORMAL_QUEUE = "normal_queue";
/** 死信交换机名称 */
private static final String DEAD_EXCHANGE = "dead_exchange";
/** 死信队列名称 */
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtil.getChannel();
// 声明交换机,类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明普通队列
Map<String, Object> arguments = new HashMap<>();
// 过期时间 单位 ms 注:也可以在生产者发送消息指定(比较灵活)
// arguments.put("x-message-ttl", 5000);
// 正常的队列要设置死信交换机 注意:key 是固定的
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信 RoutingKey
arguments.put("x-dead-letter-routing-key", "dead");
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
// 交换机和队列绑定
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "normal");
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "dead");
// 接收消息
System.out.println("Consumer01 等待接收消息...");
// 消息消费成功的回调 consumerTag:与消费者关联的消费者标签
DeliverCallback deliverCallback = (consumerTag, message) ->
System.out.println("Consumer01 接收到的消息为 ==> " + new String(message.getBody(), StandardCharsets.UTF_8));
// 消息消费失败的回调
CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, cancelCallback);
}
}
3) Producer code
/**
* @desc
* @auth llp
* @date 2022年08月04日 23:16
*/
public class Producer {
/** 普通交换机名称 */
private static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtil.getChannel();
// 死信消息 设置 TTL 时间 time to live 单位 ms
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder().expiration("5000").build();
for (int i = 1; i <= 10; i++) {
String msg = "message" + i;
channel.basicPublish(NORMAL_EXCHANGE, "normal", properties, msg.getBytes());
}
}
}
4) Consumer 02 Code
/**
* @desc
* @auth llp
* @date 2022年08月04日 22:48
*/
public class Consumer02 {
/** 死信队列名称 */
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtil.getChannel();
// 接收消息
System.out.println("Consumer02 等待接收消息...");
// 消息消费成功的回调 consumerTag:与消费者关联的消费者标签
DeliverCallback deliverCallback = (consumerTag, message) ->
System.out.println("Consumer02 接收到的消息为 ==> " + new String(message.getBody(), StandardCharsets.UTF_8));
// 消息消费失败的回调
CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}
5) Message TTL expired
消费者01
Start the code first , declare the corresponding switch and message queue, and then stop 消费者01
. Restart 生产者
, and send a message with TTL set to the peer.
Test Results:
Start
消费者02
consuming messages in the dead letter queue
6) Queue reaches maximum length
消费者01
Add the following code:
// 设置普通队列的长度 注意:key 是固定的
arguments.put("x-max-length", 6);
生产者
Remove the code for the expiration time
// 第三个参数设置为 null
channel.basicPublish(NORMAL_EXCHANGE, "normal", null, msg.getBytes());
Notice:At this time, the original queue needs to be deleted first, because the parameter setting has changed
run test
消费者01
After restarting, you need to turn it off to prevent consumption of messages, resulting in a backlog of messages. Start 生产者
it to see if four messages are sent to the dead letter queue.
7) Message rejected
Comment out the maximum length parameter setting of the queue, and the 消费者01
modified code is:
// 消息消费成功的回调 consumerTag:与消费者关联的消费者标签
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
if ("message5".equals(msg)){
System.out.println("Consumer01 拒绝的消息为 ==> " + msg);
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
}else {
System.out.println("Consumer01 接收到的消息为 ==> " + msg);
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
// 消息消费失败的回调
CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
// 手动应答 参数2:false
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);
run test