1. 死信及死信队列
1.1 什么是死信
一般来说,生产者将消息投递到队列中,消费者从队列取出消息进行消费,但某些时候由于特定的原因导致队列中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信(Dead Letter),所有的死信都会放到死信队列中。
为什么为有死信?消息变成死信一般是以下三种情况:
- 消息被拒绝,即
basicReject/basicNack
,并且设置 requeue 参数为 false,这种情况一般消息丢失 。 - 消息过期(TTL),TTL全称为Time-To-Live,表示的是消息的有效期,默认情况下 Rabbit 中的消息不过期,但是可以设置队列的过期时间和消息的过期时间以达到消息过期的效果 ,消息如果在队列中一直没有被消费并且存在时间超过了TTL,消息就会变成了"死信" ,后续无法再被消费。
- 队列达到最大长度,一般当设置了最大队列长度或大小并达到最大值时。
1.2 死信交换器 DLX
在消息的拒绝操作都是在requeue = true
情形下,如果为 false 可以发现当发生异常确认后,消息丢失了,这肯定是不能容忍的,所以提出了死信交换器(dead-letter-exchange)的概念。
死信交换器仍然只是一个普通的交换器,创建时并没有特别要求和操作。在创建队列的时候,声明该交换器将用作保存被拒绝的消息即可,相关的参数是 x-dead-letter-exchange
。当这个队列中有死信时,RabbitMQ 就会自动的将这个消息重新发布到设置的 Exchange 上去,进而被路由到另一个队列。
举个栗子
1、生产者生产 3 条消息
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class DlxProducer {
public final static String EXCHANGE_NAME = "dlx_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
Connection connection = RabbitMQUtils.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 指定转发
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String[] routekeys = {
"rabbit", "cat", "dog"};
for (int i = 0; i < 3; i++) {
String routekey = routekeys[i % 3];
String msg = "Hello,RabbitMq" + (i + 1);
channel.basicPublish(EXCHANGE_NAME, routekey, null, msg.getBytes());
System.out.println("Sent " + routekey + ":" + msg);
}
// 关闭频道和连接
channel.close();
connection.close();
}
}
2、普通消费者消费消息,但是不能消费全部的消息,并把不能消费得消息投递到死信队列。如果是我们还想做点其他事情,我们可以在死信交换的时候改变死信消息的路由键,具体的相关的参数是 x-dead-letter-routing-key
。
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 类说明:普通的消费者,但是自己无法消费的消息,将投入死信队列
*/
public class NormalDlxConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
//建立连接
Connection connection = RabbitMQUtils.getConnection();
// 创建一个信道
Channel channel = connection.createChannel();
channel.exchangeDeclare(DlxProducer.EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//绑定死信交换器
//声明一个队列,并绑定死信交换器
String queueName = "dlx_queue";
Map<String, Object> argos = new HashMap<String, Object>();
argos.put("x-dead-letter-exchange", DlxConsumer.DLX_EXCHANGE_NAME);
//死信路由键,会替换消息原来的路由键
//args.put("x-dead-letter-routing-key", "dead");
channel.queueDeclare(queueName, false, true, false, argos);
//绑定,将队列和交换器通过路由键进行绑定
channel.queueBind(queueName, DlxProducer.EXCHANGE_NAME, "#");
System.out.println("waiting for message........");
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
//如果是cat的消息确认
if (envelope.getRoutingKey().equals("cat")) {
System.out.println("Received[" + envelope.getRoutingKey() + "]" + message);
channel.basicAck(envelope.getDeliveryTag(), false);
} else {
//如果是其他的消息拒绝(queue=false),成为死信消息
System.out.println("Will reject[" + envelope