大伙可以到我的RabbitMQ专栏获取更多信息
demo示例这里拿
概述
死信队列,缩写DLX(dead letter exchange 死信交换机),当消息称为dead message之后,会被重新发送到另一个交换机,这个交换机就是DLX。
当msg在Queue中过期之后,并没有直接被丢弃,而是会被当前的Queue转发给死信交换机DLX,DLX再重新发送给另外一个Queue供其他消费者消费掉
为什么缩写不是DLQ或者是死信交换机呢?
因为在其他MQ产品中是没有交换机这个概念的,所以在RabbitMQ中也叫死信队列了,实际是死信交换机
消息在什么情况下会称为死信
- 过期的消息:队列中的消息到达了过期时间还没有被消费,就会变成死信
- 队列中消息的长度到达限制:超过某个队列的最大存储消息个数之后的,其他被exchange分发到该队列的消息直接称为死信队列
- 消费端拒接消息:当消费者端手动确认模式下拒绝了某条消息,并且设置了requeue=false之后,这条消息并不会被放回原队列,而是变为死信
队列绑定死信交换机
给队列设置参数:x-dead-letter-exchange和x-dead-letter-routing-key
测试示例
发送代码示例
以下不同情况的测试自行修改发送消息代码
/*
* 功能描述: <br>
* 〈测试死信队列〉
* 1、过期的消息:队列中的消息到达了过期时间还没有被消费,就会变成死信
* 2、队列中消息的长度到达限制:超过某个队列的最大存储消息个数之后的,其他被exchange分发到该队列的消息直接称为死信队列
* 3、消费端拒接消息:当消费者端手动确认模式下拒绝了某条消息,并且设置了requeue=false之后,这条消息并不会被放回原队列,而是变为死信
* @Param:
* @Return:
* @Author: LeoLee
* @Date: 2020/11/8 14:50
*/
@Test
public void testDLX() {
//这些消息将会被发到正常队列中
for (int i = 0; i < 10; i++) {
//该条消息测试队列过期
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "ttl.abc", "test msg dlx");
}
}
创建死信交换机和死信队列并将两者绑定
死信交换机和死信队列的创建没有什么特别的地方
当死信交换机和死信队列创建并绑定完成后,要设置正常队列对应的死信交换机以及被该正常队列丢弃的死信的routing key。该routing key决定了死信交换机接收到死信之后分发给哪个一个死信队列(死信队列可能不止一个)
package com.leolee.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName RabbitMQConfig
* @Description: TODO
* @Author LeoLee
* @Date 2020/11/7
* @Version V1.0
**/
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String DEAD_LETTER_EXCHANGE_NAME = "dlx_exchange";
public static final String QUEUE_NAME = "boot_queue";
public static final String QUEUE_TTL_NAME = "ttl_queue";
public static final String DEAD_LETTER_QUEUE_NAME = "dlx_queue";
//交换机
@Bean("bootExchange")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//死信交换机
@Bean("deadLetterExchange")
public Exchange deadLetterExchange() {
return ExchangeBuilder.topicExchange(DEAD_LETTER_EXCHANGE_NAME).durable(true).build();
}
//队列
@Bean("bootQueue")
public Queue bootQueue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
//设置TTL的队列
@Bean("queueWithTTL")
public Queue queueWithTTL() {
Map<String, Object> argumentsMap = new HashMap<>();
//设置队列超时时间
argumentsMap.put("x-message-ttl", 50000);
//设置该队列的长度
argumentsMap.put("x-max-length", 10);
//设置该队列的死信队列
argumentsMap.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_NAME);
//设置死信的routing key,能保证在死信交换机中能匹配到死信队列即可
argumentsMap.put("x-dead-letter-routing-key", "dlx.test");
return QueueBuilder.durable(QUEUE_TTL_NAME).withArguments(argumentsMap).build();
}
//对应死信交换机的队列
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
return QueueBuilder.durable(DEAD_LETTER_QUEUE_NAME).build();
}
//交换机队列绑定关系
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();//noargs,不传递参数
}
//绑定TTL队列到交换机
@Bean
public Binding bingQueueTTLExchange(@Qualifier("queueWithTTL") Queue queue, @Qualifier("bootExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
}
//绑定死信队列到死信交换机
@Bean
public Binding bingDLQToDLX(@Qualifier("deadLetterQueue") Queue queue, @Qualifier("deadLetterExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
}
过期时间死信
运行代码发送了10条消息,正常队列ttl_queue接收到消息:
过期之后:
- ttl_queue将过期的消息发送给了死信交换机dlx_exchange
- 死信交换机dlx_exchange开始根据ttl_queue设置的死信routing key[dlx.test]匹配死信队列
- 匹配到死信队列dlx_queue,将10条过期消息发送给dlx_queue等待被消费
队列过长消息死信
队列ttl_queue设置了[x-max-length:10],最大容纳10条消息在队列中等待消费,之后被分发给ttl_queue的消息直接变成死信队列
发送15条消息到ttl_queue:
由于超过10跳的长度,剩余的5条直接被丢弃给死信队列
剩下的10条在队列ttl_queue中等待消费或者是过期,由于我没有消费者去消费,剩下10条消息在过期时候也被传递给了死信队列,加上上一次测试的10条死信,一共是25条死信:
消费者拒绝死信
消费者代码复用之前讲解 Consumer Ack的代码:
设置channel.basicNack(deliveryTag, multiple, requeue)中requeue为false
划重点:requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息,false的话会被转发到该队列独赢的死信队列,如果没有对应的死信队列则消息被丢弃
package com.leolee.rabbitmq.MsgListener;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* @ClassName AckListener
* @Description: Consumer Ack
* 1.设置手动确认签收:acknowledge-mode: manual, retry.enabled: true #是否支持重试
* 2.实现ChannelAwareMessageListener接口,ChannelAwareMessageListener是MessageListener的子接口
* 3.如果消息接收并处理完成,调用channel.basicAck()向MQ确认签收
* 4.如果消息接收但是业务处理失败,调用channel.basicNack()拒收,要求MQ重新发送
* @Author LeoLee
* @Date 2020/11/7
* @Version V1.0
**/
@Component
public class DLXListener implements ChannelAwareMessageListener {
@RabbitListener(queues = "ttl_queue")
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Thread.sleep(1000);
boolean tag = new String(message.getBody()).contains("true");
System.out.println("接收到msg:" + new String(message.getBody()));
//获取mes deliveryTag
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
if (tag) {
System.out.println("业务处理成功");
//手动签收
/*
* deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
*/
channel.basicAck(deliveryTag, false);
} else {
//模拟业务处理失败抛出异常
System.out.println("业务处理失败");
throw new IOException("业务处理失败");
}
} catch (IOException e) {
e.printStackTrace();
/*
* deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
* requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息,false的话会被转发到该队列独赢的死信队列,如果没有对应的死信队列则消息被丢弃
*/
channel.basicNack(deliveryTag, false, false);
//也可以使用channel.basicReject(deliveryTag, requeue),它只能拒收单条消息
//channel.basicReject(deliveryTag, true);
}
}
}
发送两条内容信息没有包含字符串 "true" 的消息到ttl_queue:
消费者拒收了这两条消息
被拒收的消息被拒收回到ttl_queue之后,ttl_queue根据拒收参数requeue=false判断:这两条消息为死信,转发给死信交换机,最终被死信队列dlx_queue接收,加上之前测试的25条死信,一共27条: