6. RabbitMQ 死信队列的详细操作编写
@[toc]
1. 死信的概念
先从概念上解释上搞清楚这个定义,死信,顾名思义就是无法被消费者读取/消费的消息 。字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费 ,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有与之对应的 ------> 死信队列 。
应用场景: 为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中,还有比如说:用户在商城下单成功并点击去支付后,在指定时候未支付时,自动失效。
死信的来源: (死信的常见方式,大概时如下三种方式:)
- 消息的
TTL
过期。 - 队列达到最大长度(队列满了,无法再添加数据到 RabbitMQ 当中了)
- 当消息被拒绝(
basic.reject 或 basic.nack
) 并且requeue = fasle
。
死信的实战案例:
2. 消息 TTL 过期(触发死信队列)
生产者代码
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 死信队列------> 生产者代码
*/
public class Producer {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 死信消息,设置 TTL时间
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder().expiration("10000").build();
for (int i = 0; i < 11; i++) {
String message = "info" + i;
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
//channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
}
}
}
消费者 C1 代码(启动之后关闭该消费者 模拟其接收不到消息)
特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(正常队列)
*/
public class Consumer01 {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 普通队列的名称
public static final String NORMAL_QUEUE = "normal_queue";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明普通队列
Map<String, Object> arguments = new HashMap<>();
// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
//arguments.put("x-message-ttl",100000);
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 声明的是一个正常的队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
//////////////////////////////////////
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
System.out.println("等待接收消息");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
消费者 C2 代码(以上步骤完成后 启动 C2 消费者 它消费死信队列里面的消息)
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(死信队列)
*/
public class Consumer02 {
;
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
3. 队列超过队列的最大长度(触发死信队列)
- 基于上面代码:将生产者代码去掉 TTL 属性
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 死信队列------> 生产者代码
*/
public class Producer {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 死信消息,设置 TTL时间
//AMQP.BasicProperties properties =
// new AMQP.BasicProperties()
// .builder().expiration("10000").build();
for (int i = 0; i < 11; i++) {
String message = "info" + i;
//channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
}
}
}
- C1 消费者修改以下代码(启动之后关闭该消费者 模拟其接收不到消息)
注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(正常队列)
*/
public class Consumer01 {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 普通队列的名称
public static final String NORMAL_QUEUE = "normal_queue";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明普通队列
Map<String, Object> arguments = new HashMap<>();
// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
//arguments.put("x-message-ttl",100000);
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写
arguments.put("x-max-length",6);
// 声明的是一个正常的队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
//////////////////////////////////////
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
System.out.println("等待接收消息");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer01 正常队列接收到的消息" + new String(message.getBody(), "UTF-8"));
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
同时 C1 正常队列也是读取到信息了
然后,启动之后关闭该消费者C1, 模拟其接收不到消息
- C2 消费者代码不变(启动 C2 消费者)
启动消费者 1 然后再启动消费者 2
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(死信队列)
*/
public class Consumer02 {
;
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
4. 消息被拒(触发死信队列)
- .消息生产者代码同上生产者一致,不做修改
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 死信队列------> 生产者代码
*/
public class Producer {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 死信消息,设置 TTL时间
//AMQP.BasicProperties properties =
// new AMQP.BasicProperties()
// .builder().expiration("10000").build();
for (int i = 0; i < 11; i++) {
String message = "info" + i;
//channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", null, message.getBytes());
}
}
}
- C1 消费者代码(启动之后关闭该消费者 模拟其接收不到消息) 添加上拒绝消息的操作。
注意此时需要把原先队列删除 因为参数改变了,因为 RabbitMQ 不可以将一个一开始形态的队列,修改成另外一个形态的队列。所以需要删除后在创建。
java
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
// 获取到消息信息
String msg = new String(message.getBody(), "UTF-8");
if (msg.equals("info5")) {
System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");
} else {
System.out.println("Consumer01 接收的消息: " + msg);
}
};
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(正常队列)
*/
public class Consumer01 {
// 普通交换机的名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 普通队列的名称
public static final String NORMAL_QUEUE = "normal_queue";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明普通队列
Map<String, Object> arguments = new HashMap<>();
// 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写
//arguments.put("x-message-ttl",100000);
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信RoutingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写
//arguments.put("x-max-length", 6);
// 声明的是一个正常的队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
//////////////////////////////////////
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
// 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
System.out.println("等待接收消息");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
// 获取到消息信息
String msg = new String(message.getBody(), "UTF-8");
if (msg.equals("info5")) {
System.out.println("Consumer01 接收的消息是: " + msg + "此消息被 Consumer01 拒绝了");
// 拒绝对应 message.getEnvelope().getDeliveryTag() 的信息
channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
} else {
System.out.println("Consumer01 接收的消息: " + msg);
// 手动应答,接收消息
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
};
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, consumerTag -> {
});
}
}
- C2 消费者代码不变;启动消费者 1 然后再启动消费者 2
java
package com.rainbowsea.rabbitmq.eight;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rainbowsea.rabbitmq.utils.RabbitMQUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列-消费者1(死信队列)
*/
public class Consumer02 {
;
// 死信交换机的名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 死信队列的名称
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机类型为 direct
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 绑定死信的交换机与死信的队列进行一个绑定(第一个参数是队列,第二个参数是交换机)
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
// 接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer02 死信接收到的消息" + new String(message.getBody(), "UTF-8"));
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, consumerTag -> {
});
}
}
总结: 上述的,消息TTL过期,队列超过最大长度,消息被拒等等这三种方式,都是让消息无法被正常消费者读取掉,由于消费者没有读取到该消息,有为了防止,该消息被丢失,所以就将其消息放入到一个死信交换机------>死信队列中 ,最后让一个死信消费者读取到保存起来,这样队列当中的消息就没有被丢失了。
特别说明: 配置正常队列,无法处理的队列信息,转发给死信队列的,配置,只需在该正常队列当中配置映射死信队列的信息配置即可,而死信队列就是作为一个正常的队列处理读取/消费死信队列当中的内容即可。
java// 普通交换机的名称 public static final String NORMAL_EXCHANGE = "normal_exchange"; // 死信交换机的名称 public static final String DEAD_EXCHANGE = "dead_exchange"; // 普通队列的名称 public static final String NORMAL_QUEUE = "normal_queue"; // 死信队列的名称 public static final String DEAD_QUEUE = "dead_queue"; Channel channel = RabbitMQUtils.getChannel(); // 声明死信和普通交换机类型为 direct channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT); channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT); // 声明普通队列 Map<String, Object> arguments = new HashMap<>(); // 过期时间 10s = 10000ms 参数 key 是固定值的不可以随便写 //arguments.put("x-message-ttl",100000); //正常队列设置死信交换机 参数 key 是固定值 arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE); // 设置死信RoutingKey arguments.put("x-dead-letter-routing-key", "lisi"); // 设置正常队列的长度的限制,x-max-length参数 key 是固定值的,不可以随便写 //arguments.put("x-max-length", 6); // 声明的是一个正常的队列 channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments); ////////////////////////////////////// // 声明死信队列 channel.queueDeclare(DEAD_QUEUE, false, false, false, null); // 绑定普通的交换机与普通的队列进行一个绑定(第一个参数是队列,第二个参数是交换机) channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan"); System.out.println("等待接收消息");
5. 最后:
"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"