死信队列-Dead Letter
什么是死信交换机和死信队列
死信交换机:"死信"的消息经由一个交换机到达另一个队列,这样用于处理”死信“的交换机就是死信交换机(dead-letter-exchange,DLX)
死信队列:与死信交换机绑定的队列就是死信队列。“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
消息什么时候变成死信
“死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下三种情况,那么该消息将成为“死信”。
- 消息在队列的存活时间超过设置的TTL时间(如果队列和消息都是设置了TTL,将以最小的为主)
- 消息被否定确认,使用
channel.basicNack
或channel.basicReject
,并且此时requeue
属性被设置为false
; - 消息队列的消息数量已经超过最大队列长度/最大大小限制;
死信处理过程
-
DLX 也是一个正常的 Exchange,和一般的 Exchange 没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性(x-dead-letter-exchange)。
-
当这个队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的 dead-letter-exchange 上去,进而被路由到死信队列。
-
死信队列对应的消费端可以监听这个队列中的消息做相应的处理。
死信队列如何使用
1. 引入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
2. 创建配置类
public class RabbitMQConfig {
private RabbitMQConfig() {
}
// 主机IP
public static final String HOST = "127.0.0.1";
// 主机port
public static final Integer PORT = 5672;
// 主机port
public static final String VHOST = "/";
// 主机port
public static final String USERNAME = "admin";
// 主机port
public static final String PASSWORD = "admin";
// 交换机名称
public static final String DIRECT_EXCHANGE = "direct_exchange";
// 队列名称
public static final String QUEUE_NAME = "direct_queue";
// Routing key
public static final String ROUTING_KEY = "kiss";
// 死信交换机名称
public static final String DEAD_LETTER_EXCHANGE = "DLX_exchange";
// 死信队列名称
public static final String DEAD_LETTER_QUEUE = "DLX_queue";
// 死信交换机和死信队列 Routing key, # 代表所有消息
public static final String DEAD_LETTER_ROUTING_KEY = "#";
}
3. 消费端代码
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class MessageConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
// 设置主机IP
factory.setHost(RabbitMQConfig.HOST);
// 设置端口
factory.setPort(RabbitMQConfig.PORT);
// 设置 Vhost
factory.setVirtualHost(RabbitMQConfig.VHOST);
// 设置访问用户
factory.setUsername(RabbitMQConfig.USERNAME);
factory.setPassword(RabbitMQConfig.PASSWORD);
// 建立连接
Connection connection = factory.newConnection();
// 创建Channel消息通道
Channel channel = connection.createChannel();
/**
* 声明交换机,参数String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
*
* String exchange:指定交换机名称
* String type:路由类型,direct、topic、fanout
* boolean durable:是否持久化
* boolean autoDelete:是否自动删除
* Map<String, Object> arguments:其他参数
*/
channel.exchangeDeclare(RabbitMQConfig.DIRECT_EXCHANGE, "direct", false, false, null);
// 设置队列参数:x-dead-letter-exchange为指定当前队列的死信交换机
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", RabbitMQConfig.DEAD_LETTER_EXCHANGE);
/**
* 声明队列,参数String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* String queue:指定队列名称
* boolean durable:是否持久化
* boolean exclusive:是否排他,既是否创建者私有,如果为true,会对当前队列加锁,其他通道不能访问,并且
* 在连接关闭时会自动删除,不受持久化和自动删除限制
* boolean autoDelete:是否自动删除
* Map<String, Object> arguments:其他参数
*/
channel.queueDeclare(RabbitMQConfig.QUEUE_NAME, false, false, false, arguments);
/**
* 绑定交换和队列,参数String queue, String exchange, String routingKey, Map<String, Object> arguments
*/
channel.queueBind(RabbitMQConfig.QUEUE_NAME, RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, null);
/*********************声明死信交换机和死信队列,并绑定*************************/
channel.exchangeDeclare(RabbitMQConfig.DEAD_LETTER_EXCHANGE, "topic", false, false, null);
channel.queueDeclare(RabbitMQConfig.DEAD_LETTER_QUEUE, false, false, false, null);
channel.queueBind(RabbitMQConfig.DEAD_LETTER_QUEUE, RabbitMQConfig.DEAD_LETTER_EXCHANGE,
RabbitMQConfig.DEAD_LETTER_ROUTING_KEY);
// 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// 获取消息
String msg = new String(body, "UTF-8");
System.out.println("consumer message:" + msg);
}
};
// 开始获取消息String queue, boolean autoAck, Consumer callback
// channel.basicConsume(RabbitMQConfig.QUEUE_NAME, true, consumer);
}
}
4. 生产端代码
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 生产者
*/
public class MessageProvider {
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
// 设置主机IP
factory.setHost(RabbitMQConfig.HOST);
// 设置端口
factory.setPort(RabbitMQConfig.PORT);
// 设置 Vhost
factory.setVirtualHost(RabbitMQConfig.VHOST);
// 设置访问用户
factory.setUsername(RabbitMQConfig.USERNAME);
factory.setPassword(RabbitMQConfig.PASSWORD);
try (
// 建立连接
Connection connection = factory.newConnection();
// 创建Channel消息通道
Channel channel = connection.createChannel();
) {
// 设置属性,消息5秒后过期
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)// 持久化消息
.contentEncoding("UTF-8")
.expiration("5000")// TTL-Time to live
.build();
String msg = "阁下可是常山赵子龙";
channel.basicPublish(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY, properties, msg.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}