死信和死信队列
什么是死信:
死信(Dead-Letter)是指无法被正确处理的消息,这些消息会被发送到死信队列(Dead-Letter Queue),以便进一步处理。
什么时候会产生死信:
消息被拒绝(basic.reject/ basic.nack),并且 requeue=false。
消息过期。
发送消息时设置的消息保存时间,如果消息的保存时间已到,但是没有被消费。
指定某个队列中所有的消息的保存时间,如果消息的保存时间已到,但是没有被消费。
队列达到最大长度,再过来的消息会变为死信。
执行流程
当队列中的消息成为死信后,可以手动创建死信交换机(DLX),
DLX 接收死信队列中的消息,然后根据一定的规则将消息路由到指定的队列中进行处理。
实现死信队列
通过idea创建一个springboot项目后
添加依赖
<!--RabbitMQ依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
创建一个构建连接的工具类
@Configuration
public class DeadLetterConfig {
/**
* NORMAL_EXCHANGE:普通交换机
* NORMAL_QUEUE:普通队列
* NORMAL_ROUTING_KEY:普通路由键
*/
public static final String NORMAL_EXCHANGE = "normal-exchange";
public static final String NORMAL_QUEUE = "normal-queue";
public static final String NORMAL_ROUTING_KEY = "normal.#";
/**
* DEAD_EXCHANGE:死信交换机
* DEAD_QUEUE:死信队列
* DEAD_ROUTING_KEY:死信路由键
*/
public static final String DEAD_EXCHANGE = "dead-exchange";
public static final String DEAD_QUEUE = "dead-queue";
public static final String DEAD_ROUTING_KEY = "dead.#";
@Bean
public Exchange normalExchange(){
//构建普通交换机
return ExchangeBuilder.topicExchange(NORMAL_EXCHANGE).build();
}
@Bean
public Queue normalQueue(){
//构建普通队列,且绑定当消息成为死信后新的路由键和死信交换机
return QueueBuilder.durable(NORMAL_QUEUE).deadLetterExchange(DEAD_EXCHANGE).deadLetterRoutingKey("dead.abc").build();
}
@Bean
public Binding normalBinding(Queue normalQueue, Exchange normalExchange){
//绑定普通交换机和普通队列
return BindingBuilder.bind(normalQueue).to(normalExchange).with(NORMAL_ROUTING_KEY).noargs();
}
@Bean
public Exchange deadExchange(){
//构建死信交换机
return ExchangeBuilder.topicExchange(DEAD_EXCHANGE).build();
}
@Bean
public Queue deadQueue(){
//构建死信队列
return QueueBuilder.durable(DEAD_QUEUE).build();
}
@Bean
public Binding deadBinding(Queue deadQueue,Exchange deadExchange){
//绑定死信交换机和死信队列
return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs();
}
}
配置application.yml
spring:
rabbitmq:
host: xxx.xxx.xxx.xxx
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
acknowledge-mode: manual #开启手动ack
prefetch: 1
创建生产者
@SpringBootTest
public class DeadPublish {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
public void publish(){
String msg = "dead Letter";
rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE,"normal.abc",msg);
}
}
创建消费者,通过拒绝消息,将消息发送到死信队列
@Component
public class ConsumeListener {
/**
* 监听消息并拒绝
*/
@RabbitListener(queues = DeadLetterConfig.NORMAL_QUEUE)
public void consume2(String msg, Channel channel, Message message) throws IOException {
System.out.println("接收到normal队列的消息:" + msg);
//拒绝消息 requeue:false
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
//或者
//channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}
}
分别启动生产者和消费者
可以看到消费者已经接收到了消息,并且因为拒绝了消息所以消息最终被放到死信队列。
将消息放入死信队列
通过设置消息的生存时间,实现消息过期时发送到死信队列
/**
* 发送消息时设置消息生存时间
*/
@Test
public void publishExpire(){
String msg = "dead letter expire";
rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE, "normal.abc", msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
return message;
}
});
}
给队列设置消息的生存时间
@Bean
public Queue normalQueue(){
return QueueBuilder.durable(NORMAL_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE)
.deadLetterRoutingKey("dead.abc")
.ttl(10000)
.build();
}
设置Queue中的消息最大长度
@Bean
public Queue normalQueue(){
return QueueBuilder.durable(NORMAL_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE)
.deadLetterRoutingKey("dead.abc")
.maxLength(1)
.build();
}
也可以通过设置消息被拒绝的次数让消息成为死信