介绍
死信就是无法被消费的消息。生产者将消息投递给broker或者直接到队列里,消费者从队列中取出消息进行消费。但是某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续处理,就变成了死信。有死信自然就有死信队列。
将不能消费的消息也就是死信放入到死信队列,后续再去处理死信队列中的消息。
应用场景:为了保证订单业务的消息不丢失,需要用到死信队列机制。用户在商城下单成功并点击去支付后,在指定时间未支付时自动失效。
死信的来源
1.消息的TTL过期,消息的存活时间。比如消息存活时间为10s,如果不进行消费,就会变成死信。
2.队列达到最大长度,比如队列满了,无法再添加数据了。
3.消息被拒绝,消息方进行应答时进行了否定应答和拒绝应答basic.reject, basic.nack 并且requeue=false不放入队列中。
死信架构图
为了演示效果:需要先启动C1,让其声明好交换机和队列,然后建立他们的关系。然后停掉C1,不然消息就会被C1消费者消费掉。再去启动生产者发送消息,再去看rabbitmq管理平台队列上消息的分布和走向。
流程分析:当消费者C1启动后,创建了普通队列和普通交换机,死信队列和死信交换机,以及它们之间的关系后,然后手动停掉。启动消息生产者(Producer)并发送十条消息,每条消息的过期时间都是10ms。可以看出,消息首先进入到普通队列,然后因为普通队列的消息过期时间到了又无法被消费成为了死信,进入到死信队列中。
代码
C1消费者
package com.xkj.org.mq.dead;
import com.rabbitmq.client.*;
import com.xkj.org.utils.RabbitMQUtil;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class Consumer01 {
// 普通交换机
public static final String NORMAL_EXCHANGE = "normal_exchange";
// 死信交换机
public static final String DEAD_EXCHANGE = "dead_exchange";
// 普通队列
public static final String NORMAL_QUEUE = "normal_queue";
// 死信队列
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtil.getChannel();
//声明普通交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
Map<String, Object> arguments = new HashMap<>();
//设置消息过期时间10s(消息过期时间一般更多的是在消息的生产者中去设置更加灵活,而不是在这里设置比较固定不能修改)
// arguments.put("x-message-ttl", 10000);
//普通队列设置死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
//设置死信routingKey
arguments.put("x-dead-letter-routing-key", "lisi");
//声明普通队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
//声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null);
//绑定普通队列与普通交换机
channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
//绑定死信队列与死信交换机
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
System.out.println("Consumer01等待接收消息...");
//消费接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer01普通队列接收到消息:"+ new String(message.getBody(), "UTF-8"));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer01 取消消息的消费");
};
channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, cancelCallback);
}
}
Producer生产者
package com.xkj.org.mq.dead;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.xkj.org.utils.RabbitMQUtil;
import java.io.IOException;
public class Producer {
// 普通交换机
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtil.getChannel();
//设置消息过期时间10s
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder().expiration("10000").build();
for (int i = 0; i < 10; i++) {
String message = "消息"+i;
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties,
message.getBytes("UTF-8"));
}
}
}
C2消费者
package com.xkj.org.mq.dead;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.xkj.org.utils.RabbitMQUtil;
import java.io.IOException;
public class Consumer02 {
// 死信队列
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtil.getChannel();
System.out.println("Consumer02等待接收消息...");
//消费接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("Consumer02普通队列接收到消息:"+ new String(message.getBody(), "UTF-8"));
};
CancelCallback cancelCallback = consumerTag -> {
System.out.println("Consumer02 取消消息的消费");
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}
C2消费者
可以看出10条消息因为ttl过期,变成死信后进入死信队列,全部被消费者C2所消费。