死信的概念
死信,顾名思义就是无法被消费的消息,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景
为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
死信的来源
消息 TTL 过期
队列达到最大长度(队列满了,无法再添加数据到 mq 中)
消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false.
代码架构图
举个例子
一、TTL 过期
消费者1 消息
package com.lian.dead;
import com.lian.RabbitMqUtils;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
public class Consumer01 {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
//声明死信交换机名称
private final static String DEAD_EXCHANGE = "dead_exchange";
//声明普通队列名称
private final static String NORMAL_QUEUE = "normal_queue";
//声明死信队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//正常队列绑定死信队列信息,因为正常队列消息会变为死信
Map<String, Object> arguments = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
arguments.put("x-dead-letter-routing-key", "lisi");
//设置过期时间 10s,可以在消费者正常队列处设置,也可以在生产者发送处设置
//map.put("x-message-ttl",100000);
//声明死信和普通队列
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
//绑定普通的交换机和队列,交换机使用 路由key(routingKey) 和队列绑定
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
//绑定死信的交换机和死信队列,和路由key
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("Consumer1 accept message is: "+ new String(message.getBody(),"UTF-8"));
/**
* 手动应答,消费者读取完mq消息后,要给生产者一个答复
* 1、消息的标记 tag
* 2、是否批量应答 false代表不批量应答信道中的消息 true代表批量
*/
//channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = (consumerTag)->{
};
//采用手动应答,默认是自动true
//boolean autoAck = false;
//接收消息
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,cancelCallback);
}
}
生产者
package com.lian.dead;
import com.lian.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
/**
* 生产者发送10条消息
* 设置过期时间10s钟,如果没有消费者消费消息的话
* 消息就自动转为死信消息
*/
public class Producer {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//死信消息,设置TTL时间 time to live 存活时间 AMQP advanced message queue protocol 高级消息队列协议
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder().expiration("10000")
.build();
for (int i = 1; i < 11; i++) {
String message = "info" + i;
/**
* 发送消息
* 1、交换机名称
* 2、路由key routingKey
* 3、是否持久化消息
* 4、消息
*/
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
System.out.println("生产者发送消息:"+message);
}
}
}
消费者2
package com.lian.dead;
import com.lian.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
/**
* 消费者2只负责 接收私信队列的消息
*/
public class Consumer02 {
//声明普通队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("Consumer2 accept message is: "+ new String(message.getBody(),"UTF-8"));
};
CancelCallback cancelCallback = (consumerTag)->{
};
//接收消息
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
}
}
二、队列达到最大长度
因为我们对队列进行了修改,一定要先将rabbitmq控制台的原有队列删除,否则会报错
生产者
public class Producer2 {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
for (int i = 1; i < 11; i++) {
String message = "info" + i;
/**
* 发送消息
* 1、交换机名称
* 2、路由key routingKey
* 3、是否持久化消息
* 4、消息
*/
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
System.out.println("生产者发送消息:"+message);
}
}
}
消费者1
//设置正常队列的长度限制,总共发送11条数据,超出6条以外的数据就进入死信队列
arguments.put("x-max-length",6);
完整代码
package com.lian.dead;
import com.lian.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.util.HashMap;
import java.util.Map;
public class Consumer03 {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
//声明死信交换机名称
private final static String DEAD_EXCHANGE = "dead_exchange";
//声明普通队列名称
private final static String NORMAL_QUEUE = "normal_queue";
//声明死信队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//正常队列绑定死信队列信息,因为正常队列消息会变为死信
Map<String, Object> arguments = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
arguments.put("x-dead-letter-routing-key", "lisi");
//设置正常队列的长度限制,总共发送11条数据,超出6条以外的数据就进入死信队列
arguments.put("x-max-length",6);
//设置过期时间 10s,可以在消费者正常队列处设置,也可以在生产者发送处设置
//map.put("x-message-ttl",100000);
//声明死信和普通队列
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
//绑定普通的交换机和队列,交换机使用 路由key(routingKey) 和队列绑定
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
//绑定死信的交换机和死信队列,和路由key
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("Consumer1 accept message is: "+ new String(message.getBody(),"UTF-8"));
/**
* 手动应答,消费者读取完mq消息后,要给生产者一个答复
* 1、消息的标记 tag
* 2、是否批量应答 false代表不批量应答信道中的消息 true代表批量
*/
//channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = (consumerTag)->{
};
//采用手动应答,默认是自动true
//boolean autoAck = false;
//接收消息
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,cancelCallback);
}
}
消费者2
/**
* 消费者2只负责 接收私信队列的消息
*/
public class Consumer02 {
//声明普通队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("Consumer2 accept message is: "+ new String(message.getBody(),"UTF-8"));
};
CancelCallback cancelCallback = (consumerTag)->{
};
//接收消息
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
}
}
测试
1、先启动消费者1,自当在rabbitmq控制台生成了 正常队列、死信队列等,然后将其关闭,模拟异常
2、启动生产者发送10条数据
因为做了 队列最大长度限制,所以超出6条的消息就进入 死信队列中
3、启动消费者2
死信的4条数据被消费者2消费
消费者2控制台打印
Consumer2 accept message is: info1
Consumer2 accept message is: info2
Consumer2 accept message is: info3
Consumer2 accept message is: info4
三、消息被拒
消费者1
public class Consumer04 {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
//声明死信交换机名称
private final static String DEAD_EXCHANGE = "dead_exchange";
//声明普通队列名称
private final static String NORMAL_QUEUE = "normal_queue";
//声明死信队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
//声明死信和普通交换机 类型为 direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//正常队列绑定死信队列信息,因为正常队列消息会变为死信
Map<String, Object> arguments = new HashMap<>();
//正常队列设置死信交换机 参数 key 是固定值
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//正常队列设置死信 routing-key 参数 key 是固定值
arguments.put("x-dead-letter-routing-key", "lisi");
//设置正常队列的长度限制,总共发送10条数据,超出6条以外的数据就进入死信队列
//arguments.put("x-max-length",6);
//设置过期时间 10s,可以在消费者正常队列处设置,也可以在生产者发送处设置
//map.put("x-message-ttl",100000);
//声明死信和普通队列
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
//绑定普通的交换机和队列,交换机使用 路由key(routingKey) 和队列绑定
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");
//绑定死信的交换机和死信队列,和路由key
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
String msg = new String(message.getBody(), "UTF-8");
if (msg.equals("info5")){
System.out.println("Consumer1 accept message is: "+ msg +"this message was rejected");
/**
* 信道拒绝
* 1、消息的 标志号
* 2、false 代表不放回到正常队列,那就成了死信
*/
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}else {
System.out.println("Consumer1 accept message is: " + msg);
/**
* 手动应答
* 1、消息标志号
* 2、false 代表不批量应答
* 3、接收消息时 要同步改为 false,代表 手动应答
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
};
CancelCallback cancelCallback = (consumerTag)->{
};
//采用手动应答,默认是自动true
//boolean autoAck = false;
//接收消息
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,cancelCallback);
}
}
生产者不变
public class Producer2 {
//声明普通交换机名称
private final static String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
for (int i = 1; i < 11; i++) {
String message = "info" + i;
/**
* 发送消息
* 1、交换机名称
* 2、路由key routingKey
* 3、是否持久化消息
* 4、消息
*/
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
System.out.println("生产者发送消息:"+message);
}
}
}
消费者2不变
/**
* 消费者2只负责 接收私信队列的消息
*/
public class Consumer02 {
//声明普通队列名称
private final static String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
//创建信道
Channel channel = RabbitMqUtils.getChannel();
System.out.println("等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message)->{
System.out.println("Consumer2 accept message is: "+ new String(message.getBody(),"UTF-8"));
};
CancelCallback cancelCallback = (consumerTag)->{
};
//接收消息
channel.basicConsume(DEAD_QUEUE,true,deliverCallback,cancelCallback);
}
}
测试
发送10条数据,9条被消费,1条进入死信队列