一、什么是死信队列
在普通队列中如果消息被拒绝、超时、队列消息满的情况,如果该队列指定了死信交互机则可以将死信的消息发送到死信队列中处理。其中,死信队列也是普通的队列只不过是被指定处理死信消息而已。
二、代码
1. 消息被拒接
生产者代码
在生产者声明了普通交换机、普通队列以及普通队列参数。x-dead-letter-exchange 声明队列交换机,x-dead-letter-routing-key 死信队列routing-key。声明死信队列的时候routing-key保持一致。
public class DeadProducer {
public static void main(String[] args) throws IOException, TimeoutException {
RabbitMqBase rabbitMqBase = new RabbitMqBase();
Map<String, Object> arg = new HashMap<>();
arg.put("x-dead-letter-exchange",RabbitMQConfig.DEAD_EXCHANGE);
arg.put("x-dead-letter-routing-key","dead-message");
rabbitMqBase.producer(
RabbitMQConfig.NORMAL_EXCHANGE,
RabbitMQConfig.NORMAL_QUEUE,
"normal",
BuiltinExchangeType.DIRECT.getType(),
"hello 2",
arg);
}
}
RabbitMqBase 对新建生产做了封装。
/**
* 创建生产者
* */
public void producer(String exchange, String queue, String routingKey,String type,String message, Map<String, Object> arg) throws IOException, TimeoutException {
//创建rabbitmq
Connection connection = RabbitMqUtil.getConnection();
//get channel
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange,type);
channel.queueDeclare(queue,false,false,false,arg);
channel.queueBind(queue,exchange,routingKey);
//发送消息
channel.basicPublish(exchange,routingKey,null,message.getBytes(StandardCharsets.UTF_8));
System.out.println("发送成功");
}
消费者代码
在消费者声明了死信交换机、队列 DEAD_QUEUE 并且绑定了两者的关系,声明方式跟声明普通的队列没什么区别。
为了测试消息被拒绝的场景,通过channel.basicReject 拒绝了消息。
/**
* 死信队列正常消费者
* */
public class NormalCustomer {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMqUtil.getConnection();
Channel channel = connection.createChannel();
//声明队列
channel.exchangeDeclare(RabbitMQConfig.DEAD_EXCHANGE,BuiltinExchangeType.DIRECT,false,false,null);
channel.queueDeclare(RabbitMQConfig.DEAD_QUEUE,false,false,false,null);
channel.queueBind(RabbitMQConfig.DEAD_QUEUE,RabbitMQConfig.DEAD_EXCHANGE,"dead-message");
channel.basicConsume(RabbitMQConfig.NORMAL_QUEUE,false,((consumerTag, message) -> {
System.out.println(RabbitMQConfig.NORMAL_QUEUE+"拒绝消息:"+new String(message.getBody()));
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}),(message)->{
System.out.println("消费失败");
});
}
}
死信队列消费者
跟普通的消费者没什么区别,只不过他监听了死信队列处理相关逻辑。
/**
* 死信队列
* */
public class DeadCustomer {
public static void main(String[] args) throws IOException {
RabbitMqBase rabbitMqBase = new RabbitMqBase();
rabbitMqBase.customer(RabbitMQConfig.DEAD_QUEUE,true,((consumerTag, message) -> {
System.out.println("死信队列处理消息:"+new String(message.getBody()));
}),(consumerTag -> {}));
}
}
测试结果
- 生产者:发送被普通队列拒绝的消息
- 普通消费者:NORMAL_QUEUE拒绝消息:发送被普通队列拒绝的消息
- 死信消费者:死信队列处理消息:发送被普通队列拒绝的消息
生成交换机信息:
生成队列信息:
2. 队列限制情况
我们将对普通队列限制最大接收消息条数为6,若队列消息大于6条其他进来的消息则发送到死信队列。
生产者代码
新增队列参数x-max-length 设置为6条。其余跟上代码一致。
/**
* 死信队列
* */
public class DeadProducer {
public static void main(String[] args) throws IOException, TimeoutException {
RabbitMqBase rabbitMqBase = new RabbitMqBase();
Map<String, Object> arg = new HashMap<>();
arg.put("x-dead-letter-exchange",RabbitMQConfig.DEAD_EXCHANGE);
arg.put("x-dead-letter-routing-key","dead-message");
arg.put("x-max-length",6);
rabbitMqBase.producer(
RabbitMQConfig.NORMAL_EXCHANGE,
RabbitMQConfig.NORMAL_QUEUE,
"normal",
BuiltinExchangeType.DIRECT.getType(),
"发送限制消息",
arg,10);
}
}
消费者代码
正常应答消息
/**
* 死信队列正常消费者
* */
public class NormalCustomer {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMqUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(RabbitMQConfig.DEAD_EXCHANGE,BuiltinExchangeType.DIRECT,false,false,null);
channel.queueDeclare(RabbitMQConfig.DEAD_QUEUE,false,false,false,null);
channel.queueBind(RabbitMQConfig.DEAD_QUEUE,RabbitMQConfig.DEAD_EXCHANGE,"dead-message");
channel.basicConsume(RabbitMQConfig.NORMAL_QUEUE,false,((consumerTag, message) -> {
System.out.println(RabbitMQConfig.NORMAL_QUEUE+"接受消息:"+new String(message.getBody()));
channel.basicAck(message.getEnvelope().getDeliveryTag(),true);
}),(message)->{
System.out.println("消费失败");
});
}
}
测试
-
开启生产者
-
不开启普通消费者
此时队列消息数量如图所示,普通队列接收了最大限制条数6条,其余发送到死信队列。
-
开启两个消费者
此时得到消息情况为
- 死信消费者
死信队列处理消息:发送限制消息0
死信队列处理消息:发送限制消息1
死信队列处理消息:发送限制消息2
死信队列处理消息:发送限制消息3
- 普通消费者
NORMAL_QUEUE接受消息:发送限制消息4
NORMAL_QUEUE接受消息:发送限制消息5
NORMAL_QUEUE接受消息:发送限制消息6
NORMAL_QUEUE接受消息:发送限制消息7
NORMAL_QUEUE接受消息:发送限制消息8
NORMAL_QUEUE接受消息:发送限制消息9
为什么普通消费者是消费后六条消息而不是前六条消息?
这是因为队列先进先出的特性,消息是从0开始入队,第六条消息入队后才触发队列长度限制,所以先将队列0发送到死信队列,以此类推。
3. 延时队列处理
通过设置消息超时时间或者设置队列超时属性都可以实现延迟队列发送到死信队列处理的场景。此处设置了消息的超时属性。队列属性可以设置x-message-ttl 。
public class DeadProducer {
public static void main(String[] args) throws IOException, TimeoutException {
RabbitMqBase rabbitMqBase = new RabbitMqBase();
Map<String, Object> arg = new HashMap<>();
arg.put("x-dead-letter-exchange",RabbitMQConfig.DEAD_EXCHANGE);
arg.put("x-dead-letter-routing-key","dead-message");
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder().expiration("10000").build();
rabbitMqBase.producer(
RabbitMQConfig.NORMAL_EXCHANGE,
RabbitMQConfig.NORMAL_QUEUE,
"normal",
BuiltinExchangeType.DIRECT.getType(),
"测试超时死信队列",
arg,10,basicProperties);
}
}
这里可以不需要普通消费者,启动死信消费者监听即可,待消息超时后发送到死信队列处理。延迟队列的实现也是通过这种方式。可以运用于订单未支付超时处理等场景。