先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理 解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有 后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息 消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时 间未支付时自动失效
死信的来源
TTL
过期mq
中)basic.reject
或 basic.nack
)并且 requeue=false
.死信的流程图
什么是TTL,TTL是Rabbitmq中的一个消息或者队列的属性,表明一条消息或者队列中的所有消息的最大存活时间,单位是毫秒。
如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这 条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的 TTL 和消息的 TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。
针对每一条消息设置TTL
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
队列设置TTL
Map<String,Object> params = new HashMap<>();
params.put("x-message-ttl",1000);
区别:
如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队 列中),而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者 之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需 要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以 直接投递该消息到消费者,否则该消息将会被丢弃。
生产者代码
/**
* 死信 TTl过期 生产者
*/
public class TtlProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
//设置消息的TTl时间
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
for (int i = 1; i < 11; i++) {
String message = "info"+i;
channel.basicPublish(ExchangeNames.NORMAL,"normal-key",properties,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发送消息:"+message);
}
}
}
消费者1代码,启动之后关闭该消费者,使其接收不到消息
/**
* 死信 TTL 消费者1
*/
public class TtlCustomer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明死信和普通交换机
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
//声明死信队列
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
//设置参数
Map<String,Object> params = new HashMap<>();
//正常队列设置死信交换机,key是固定的
params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
//正常队列设置死信的 routing-key, key是固定的
params.put("x-dead-letter-routing-key","dead-key");
params.put("x-message-ttl",1000);
//声明普通的队列
String normalQueue = "normal-queue";
channel.queueDeclare(normalQueue,false,false,false,params);
//绑定队列和交换机
channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");
System.out.println("TtlCustomer1 等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});
}
}
此时,生产者未发送消息,消费者也没有接收到消息
生产者发送10条消息,此时正常队列有10条消息没有被消费
时间过去10秒,正常队列里面的消息没有被消费,消息进入了死信队列
消费者2代码
/**
* 死信 TTL 消费者2
*/
public class TtlCustomer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明死信和普通交换机
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
//声明死信队列
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
//绑定队列和交换机
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
System.out.println("TtlCustomer2 等待接收死信队列消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
}
}
启动消费者2代码,发现死信队列中的消息被消费者2消费
我们可以给一个队列设置最大的长度,当队列中的消息达到了最大长度之后,后面的消息就转入到死信队列中。
Map<String,Object> params = new HashMap<>();
params.put("x-max-length",6);
生产者代码
/**
* 死信 队列最大长度 生产者
*/
public class MaxLengthProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
for (int i = 1; i < 11; i++) {
String message = "info"+i;
channel.basicPublish(ExchangeNames.NORMAL,"normal-key",null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发送消息:"+message);
}
}
}
消费者1代码
/**
*
* 死信 最大队列长度 消费者
*/
public class MaxLengthConsumer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明两个队列,一个普通队列,一个死信队列
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
Map<String,Object> params = new HashMap<>();
params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
params.put("x-dead-letter-routing-key","dead-key");
params.put("x-max-length",6);
String normalQueue = "normal-queue";
channel.queueDeclare(normalQueue,false,false,false,params);
channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
System.out.println("MaxLengthConsumer1 等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});
}
}
启动消费者1,查看消费未发送时的状态
关闭消费者1,启动生产者,发送消息
消费者2代码
/**
* 死信 最大队列长度 消费者
*/
public class MaxLengthConsumer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明死信交换机
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
//声明死信队列
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
//绑定队列和交换机
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
System.out.println("MaxLengthConsumer2 等待接收死信队列消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
}
}
启动消费者2,消费掉死信队列中的消息之后,如下图所示
普通队列拒绝接收消息之后,将消息转入死信队列,拒绝接收消息的方法如下所示
requeue
:设置为false
,代表拒接重新入队,该队列如果配置了死信队列那么将进入到死信队列中channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
生产者代码
/**
* 消息被拒 生产者
*/
public class RejectProducer {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
for (int i = 1; i < 11; i++) {
String message = "info"+i;
channel.basicPublish(ExchangeNames.NORMAL,"normal-key",null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("生产者发送消息:"+message);
}
}
}
消费者1代码
/**
*
* 死信 消息被拒 消费者1
*/
public class RejectConsumer1 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明两个队列,一个普通队列,一个死信队列
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
Map<String,Object> params = new HashMap<>();
params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
params.put("x-dead-letter-routing-key","dead-key");
String normalQueue = "normal-queue";
channel.queueDeclare(normalQueue,false,false,false,params);
channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
System.out.println("RejectConsumer1 等待接收消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
if (receive.equals("info5")){
System.out.println("RejectConsumer1接收到消息:"+receive+",但是拒接接收了");
//requeue 设置为false,代表拒接重新入队 该队列如果配置了死信队列那么将进入到死信队列中
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}else {
System.out.println("RejectConsumer1接收到消息:"+receive);
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
};
channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});
}
}
启动消费者1,队列信息如下所示
启动生产者,发送消息,info5被拒绝接收进入死信
消费者2代码
/**
*
* 死信 消息被拒 死信 消费者
*/
public class RejectConsumer2 {
public static void main(String[] args) throws IOException {
Channel channel = RabbitUtil.getChannel();
//声明死信交换机
channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
//声明死信队列
String deadQueue = "dead-queue";
channel.queueDeclare(deadQueue,false,false,false,null);
//绑定队列和交换机
channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");
System.out.println("RejectConsumer2 等待接收死信队列消息...");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String receive = new String(message.getBody());
System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
};
channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
}
}
启动消费者2消费死信队列中的消息