死信队列
先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效
1、死信来源
消息 TTL 过期
队列达到最大长度(队列满了,无法再添加数据到 mq 中)
消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false.
2、消息TTL过期
创建普通队列 和 交换机,还有死信队列,交换机
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static final String NORMAL_QUEUE = "normal_queue";
public static final String DEAD_EXCHANGE = "dead_exchange";
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
//声明并创建 死信交换机和队列 建立交换机与队列的映射关系
channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"dead");
//如果普通队列中出现死信消息,应该将此消息,交由死信交换机,放到对应的死信队列
HashMap<String, Object> arguments = new HashMap<>();
// 给普通队列设置死信交换机 注意:key是固定值
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
// 指定routerKey,死信交换机将数据发给哪个dead_queue
arguments.put("x-dead-letter-routing-key","dead");
channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);
//声明并创建 普通交换机和队列 建立交换机与队列的映射关系
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"normal");
DeliverCallback ackCallback = (consumerTag,delivery)->{
System.out.println(consumerTag+ " 正在消费正常消息:"+ new String(delivery.getBody()));
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
channel.basicConsume(NORMAL_QUEUE,false,ackCallback,(consumerTag)->{});
}
开启当前的消费者,发现创建了两个交换机和队列,停止当前消费者,模拟消息不处理
创建生产者,设置消息的过期时间(10s),并启动
public static final String EXCHANGE_NAME = "normal_exchange";
public static final String QUEUE_NAME = "normal_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
// 配置消息的TTL 过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().expiration(String.valueOf(10 * 1000)).build();
for (int i = 0; i < 10; i++) {
String message = "你好啊"+i;
channel.basicPublish(EXCHANGE_NAME,"normal",properties,message.getBytes("UTF-8"));
System.out.println("发送消息成功");
}
}
发现10s后,normal_queue中的消息,全部到了dead_queue
在上述过程中,其实在消费者创建队列的时候,也可以设置消息的TTL,但是其实生产者设置消息的TTL是最灵活的。
启动死信队列的消费者
public static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
DeliverCallback ackCallback = (consumerTag, delivery)->{
System.out.println(consumerTag+ " 正在消费异常消息:"+ new String(delivery.getBody()));
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
channel.basicConsume(DEAD_QUEUE,false,ackCallback,(consumerTag)->{});
}
当前消费者,成功处理死信消息,死信队列消息为0
3、队列达到最大长度
上述代码一致,需要删除原先的队列,因为参数变了,在正常队列的消费者中添加一行配置,设置当前队列,可接收的最大消息数量
// 指定正常队列的最大接收的消息数量
arguments.put("x-max-length",5);
1、启动生产者,关闭消费者,因为消费者,处理速度很快,可能发一条处理一条,那么队列长度永远也达不到5,永远满不了
可以看到,两个消费者各消费五条消息
4、消息被拒
修改普通消费者代码
DeliverCallback ackCallback = (consumerTag,delivery)->{
String message = new String(delivery.getBody());
// 模拟消息拒收, 拒收消息, 拒绝将该消息重新入队,消息发给死信交换机
if(message.contains("7")){
System.out.println("出于某种原因,拒收该消息:"+message );
channel.basicReject(delivery.getEnvelope().getDeliveryTag(),false);
}else{
System.out.println(consumerTag+ " 正在消费正常消息:"+ message);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
};