死信队列
死信队列顾名思义就是由于某些原因导致队列中的某些消息不能被消费,这样没有后面的处理之后,就变成了死信消息,而在实际应用场景中,为了保证消息的不丢失,则将死信消息放进一个特殊的队列中死信队列.
应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时间未支付时自动失效.
而导致死信消息的产生大概有三方面:
- 消息TTL过期:即在队列或者生产者中声明了消息的过期时间,如果在过期时间到了之后,消息还没有进行处理就变成了死信消息,则需要将消息发送给死信交换机再到死信队列
- 队列到达最大长度:顾名思义,在声明队列的时候,指定队列的长度,一定时间内,发送大量消息到队列中,一旦到达队列的最大长度,则将消息放进死信交换机中.
- 消费者手动拒绝消息:在消费的过程中,消费者在DeliveryCallBack的回调方法中使用Basicnack手动拒收消息,并且将requeue值为false使消息不返回队列中,前提是关闭队列的自动确认消息,在确认回调方法中使用Basicack中手动确认消息,并关掉批量确认,保证消息的安全性.
1.消息TTL过期(相当于延迟队列)
消费者声明队列和交换机:
public class Consumer01 {
/**
* NORMAL_EXCHANGE : 普通交换机
* DEAD_EXCHANGE : 死信交换机
* NORMAL_QUEUE: 普通队列
* DEAD_QUEUE: 死信队列
*/
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, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明普通队列
Map<String,Object> arguments = new HashMap<>();
/*
1. 正常队列设置死信交换机
2. 设置死信routingKey
3. 过期时间 通常由生产者设置,灵活性强
4. 正常队列的长度限制
*/
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key","lisi");
arguments.put("x-message-ttl",10000)
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");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("Consumer01接收的消息:"+msg);
};
//拒绝必须关闭掉自动确认,否则会表示消息已经被消费掉,就会造成消息的丢失
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
}
}
生产者:通过生产者也可以设置TTL过期时间,灵活性更强
public class Producer {
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
死信消息,设置TTL时间
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder().expiration("10000").build();
for (int i = 1; i < 11; i++) {
String message = "info" + i;
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}
2. 队列到达最大长度
消费者:
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
arguments.put("x-dead-letter-routing-key","lisi");
arguments.put("x-max-length",6)
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");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("Consumer01接收的消息:"+msg);
};
//拒绝必须关闭掉自动确认,否则会表示消息已经被消费掉,就会造成消息的丢失
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
}
生产者:
public class Producer {
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
for (int i = 1; i < 11; i++) {
String message = "info" + i;
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}
3.消费者手动拒绝消息
消费者:关掉自动确认,手动确认消息,手动拒绝消息并且不放回原队列
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
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");
DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
if ("info5".equals(msg)){
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
System.out.println("Consumer01接收的消息:"+msg+":此消息是被拒绝的");
}else {
System.out.println("Consumer01接收的消息:"+msg);
//手动确认消息已经接收到,不开启批量确认,处理完一条消息确认一次
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
};
//拒绝必须关闭掉自动确认,否则会表示消息已经被消费掉,就会造成消息的丢失
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag -> {});
}
生产者:
public class Producer {
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
for (int i = 1; i < 11; i++) {
String message = "info" + i;
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}