1、什么是死信
死信产生主要来自于两个过程角色
- 来自于消费者端
- 来自于queue
产生死信的三种情况:
- 如果queue中的消息被消费者接收, 但是消费者拒绝消费(消费者执行了reject 或nack 并将 requee 参数设置为 false )的时候,这个消息就会变成死信。
- 消息本身设置了过期时间(TTL), 并且消息过期时间已经生效, 还未被消费的消息就会变成死信【特点是每个消息的过期时间都不同】
- 可以设置队列中所有消息的过期时间,如果消息过期时间已经生效,消息还未被消费
- 队列设置了最大长度限制, 当队列已满, 之后从交换机路由到该队列的消息会自动变成死信。
2、什么是死信交换机
死信交换机是专门处理死信的交换机
3、什么是死信队列
跟死信交换机绑定的队列就是死信队列,死信队列中存储的消息都是死信消息。
4、死信队列的应用
- 保证消息不会丢失,保证数据的完整性;
- 可以借助延时消费的特性完成特定的功能(比如订单生成但是未支付,超过30分钟自动取消的业务场景)
5、原理图介绍
6、三种场景代码演示
6.1 消费者拒绝消费消息, 消息进入死信队列
首先需要在application.yml文件中开启手动ack
spring:
rabbitmq:
# 开启消费者手动ack
listener:
direct:
acknowledge-mode: manual
创建配置类
往容器中注入普通交换机、普通队列、路由规则和死信交换机和死信队列、死信路由规则
package com.kkarma.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DeadLettingConfig {
public static final String GENERIC_EXCHANGE = "generic-exchange";
public static final String GENERIC_QUEUE = "generic-queue";
public static final String GENERIC_ROUTING_KEY = "generic.#";
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 genericExchange(){
return ExchangeBuilder.topicExchange(GENERIC_EXCHANGE).build();
}
@Bean
public Queue genericQueue(){
return QueueBuilder.durable(GENERIC_QUEUE).deadLetterExchange(DEAD_EXCHANGE).deadLetterRoutingKey("dead.demo").build();
}
@Bean
public Binding genericBinding(Queue genericQueue, Exchange genericExchange){
return BindingBuilder.bind(genericQueue).to(genericExchange).with(GENERIC_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();
}
}
生产者端
package com.kkarma.deadletter;
import com.kkarma.config.DeadLettingConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
public class Publisher {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Publisher(RabbitTemplate rabbitTemplate){
this.rabbitTemplate = rabbitTemplate;
}
/**
* 消费者拒绝消费,消息进入死信队列
*/
@Test
public void push() throws IOException {
rabbitTemplate.convertAndSend(DeadLettingConfig.GENERIC_EXCHANGE, "generic.demo", "正常消息被拒绝消息会变成死信...");
System.out.println("消息发送成功...");
System.in.read();
}
}
消费者端
package com.kkarma.deadletter;
import com.kkarma.config.DeadLettingConfig;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@RabbitListener(queues = DeadLettingConfig.GENERIC_QUEUE)
public void pullAndNack(String msg, Channel channel, Message message) throws Exception {
System.out.println("接收generic-queue队列中的消息内容: " + msg);
String correlationId = message.getMessageProperties().getCorrelationId();
System.out.println("唯一标识: " + correlationId);
// 拒绝消费消息
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
// 确认未成功消费
// channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}
}
测试结果
6.2 消息指定过期时间
主要是生产者端在发送消息是指定消息的过期时间
/**
* 消息指定过期时间
*/
@Test
public void publishMsgExpire(){
String msg = "generic letter expire become dear letter";
rabbitTemplate.convertAndSend(DeadLettingConfig.GENERIC_EXCHANGE, "generic.demo", msg, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
}
6.3 给队列设置消息过期时间
这一项设置主要是通过DeadLettingConfig配置类中的正常队列来进行设置
@Bean
public Queue genericQueue(){
// 这里需要指定当消息变成死信时绑定的死信交换机,并且需要重新指定指定死信交换机和死信队列之间的路由
return QueueBuilder
.durable(GENERIC_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE)
.deadLetterRoutingKey("dead.demo")
// 设置队列中消息的过期时间
.ttl(10000)
.build();
}
6.4 设置队列的最大长度
@Bean
public Queue genericQueue(){
// 这里需要指定当消息变成死信时绑定的死信交换机,并且需要重新指定指定死信交换机和死信队列之间的路由
return QueueBuilder
.durable(GENERIC_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE)
.deadLetterRoutingKey("dead.demo")
// 设置队列最大长度,当队列已满,再路由过来的消息就被当成死信处理
.maxlength(10)
.build();
}