死信队列
什么是死信队列
死信队列DLX
(dead-letter-exchange
),利用DLX
,当消息在一个队列中变成死信 (dead message
) 之后,它能被重新投递到另一个exchange
上,这个exchange
就是DLX
,似乎叫死信交换机更加贴切,当死信投递到这个exchange
后,我们也可以用一个queue
来绑定该exchange
,该exchange
就可以根据路由规则把这个死信路由到这个queue
了,同样可以创建消费端去进行消费,以便对死信进行相应的处理。
消息变成死信的情况
- 消息被拒绝(
basic.reject
/ basic.nack
),并且requeue = false
(不重回队列)。 - 消息
TTL
过期。 - 队列达到最大长度。
死信处理过程
DLX
也是一个正常的exchange
,和一般的exchange
没有区别,不过,需要在队列上将它设置成DLX
,在arguments
中添加key
为x-dead-letter-exchange
,value
为该交换机名称的key-value
即可,代码如下:
// 设置死信队列
Map<String , Object> arguments = new HashMap<String , Object>();
arguments.put("x-dead-letter-exchange" , dlxExchange);
channel.exchangeDeclare(dlxExchange , "topic" , true , false , null);
channel.queueDeclare(dlxQueueName , true , false , false , null);
channel.queueBind(dlxQueueName , dlxExchange , dlxRoutingKey);
// 创建Queue
channel.queueDeclare(queueName , true , false , false , arguments);
运行上面代码之后,可以看到名称为test
的队列就有了DLX
。
当该队列中有死信时,RabbitMQ
服务器就会自动的将这个消息重新投递到设置的exchange
上去,进而被路由到另一个队列中。
我们这里只测试消息TTL
过期的情况,并且是设置消息本身的TTL
,而不是队列的TTL
。
生产端
package com.kaven.rabbitmq.api.dlx;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
// 自己服务器的IP
private static String ip = "IP";
// RabbitMQ启动的默认端口,也是应用程序进行连接RabbitMQ的端口
private static int port = 5672;
// RabbitMQ有一个 "/" 的虚拟主机
private static String virtualHost = "/";
// default exchange
private static String exchange = "";
// default exchange 的路由规则: routingKey(test) 将匹配同名的 queue(test)
private static String routingKey = "test";
public static void main(String[] args) throws IOException, TimeoutException {
// 1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(ip);
connectionFactory.setPort(port);
connectionFactory.setVirtualHost(virtualHost);
// 2 创建Connection
Connection connection = connectionFactory.newConnection();
// 3 创建Channel
Channel channel = connection.createChannel();
// 4 发送消息
for (int i = 0; i < 5; i++) {
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.expiration("10000")
.build();
String msg = "RabbitMQ: dlx message" + i;
channel.basicPublish(exchange , routingKey , properties , msg.getBytes());
}
// 5 关闭连接
channel.close();
connection.close();
}
}
消费端
package com.kaven.rabbitmq.api.dlx;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
public class MyConsumer extends DefaultConsumer {
public Channel channel;
public MyConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("------------ consumer message -----------");
System.out.println("body:" + new String(body));
}
}
package com.kaven.rabbitmq.api.dlx;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
public class Consumer {
// 自己服务器的IP
private static String ip = "IP";
// RabbitMQ启动的默认端口,也是应用程序进行连接RabbitMQ的端口
private static int port = 5672;
// RabbitMQ有一个 "/" 的虚拟主机
private static String virtualHost = "/";
// default exchange
private static String exchange = "";
// 队列名
private static String queueName = "test";
// 死信队列的设置
private static String dlxExchange = "dlx.exchange";
private static String dlxQueueName = "dlx.queue";
private static String dlxRoutingKey = "#";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(ip);
connectionFactory.setPort(port);
connectionFactory.setVirtualHost(virtualHost);
// 2 创建Connection
Connection connection = connectionFactory.newConnection();
// 3 创建Channel
Channel channel = connection.createChannel();
// 4 设置死信队列
Map<String , Object> arguments = new HashMap<String , Object>();
arguments.put("x-dead-letter-exchange" , dlxExchange);
channel.exchangeDeclare(dlxExchange , "topic" , true , false , null);
channel.queueDeclare(dlxQueueName , true , false , false , null);
channel.queueBind(dlxQueueName , dlxExchange , dlxRoutingKey);
// 5 创建Queue
channel.queueDeclare(queueName , true , false , false , arguments);
// 6 消费消息
// channel.basicConsume(queueName , true , new MyConsumer(channel));
}
}
// 死信队列的设置
private static String dlxExchange = "dlx.exchange";
private static String dlxQueueName = "dlx.queue";
private static String dlxRoutingKey = "#";
这是为死信队列设置的相关参数,交换机名称、队列名称、交换机与队列绑定的routingKey
,这里的交换机是topic
类型的,routingKey="#"
是当消息变成死信时,该消息就会被投递到该交换机(dlx.exchange
)上,根据这个routingKey
,可以让任何在该交换机(dlx.exchange
)上的消息都可以路由到该队列(dlx.queue
)中。
// 设置死信队列
Map<String , Object> arguments = new HashMap<String , Object>();
arguments.put("x-dead-letter-exchange" , dlxExchange);
channel.exchangeDeclare(dlxExchange , "topic" , true , false , null);
channel.queueDeclare(dlxQueueName , true , false , false , null);
channel.queueBind(dlxQueueName , dlxExchange , dlxRoutingKey);
定义死信队列的交换机(dlx.exchange
)、队列(dlx.queue
),并且绑定该交换机和队列,并且设置routingKey
(dlxRoutingKey = "#"
)。
// 创建Queue
channel.queueDeclare(queueName , true , false , false , arguments);
定义队列时,为队列添加一个DLX
(在arguments
参数中体现)。
我们这里也把消费端的消费消息给关闭了,方便测试消息过期后是否会被投递到死信队列中。
测试
启动生产端和消费端,看看RabbitMQ Management
,之所以有10
条消息,是因为我测试了两次。
10
秒种内,消息还没过期,还在test
中。
10
秒种后,消息就过期了,消息便到dlx.queue
中了,而test
中的消息就没有了,很明显符合预期。