先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer 从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。
应用场景:为了保证订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息消费发生异常时,将消息投入死信队列中.还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效。
由上图可知,需要有两个交换机(普通交换机、死信交换机),两个队列(普通队列、死信队列)。模拟三种成为死信的方式。
1、消息TTL过期
package com.example.demo08;
import com.example.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author 我见青山多妩媚
* @date Create on 2022/8/20 21:59
* 死信队列之生产者
*/
public class Producer {
//普通交换机名称
private static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//设置消息的过期时间 10s = 10000ms
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder()
.expiration("10000")
.build();
//死信消息 设置TTL(time to live)时间
for (int i = 0; i < 10; i++) {
String message = "info:"+(i+1) + "";
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
}
}
}
package com.example.demo08;
import com.example.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author 我见青山多妩媚
* @date Create on 2022/8/20 21:33
* 死信队列实战:消费者1
*/
public class Consumer01 {
//普通交换机
private static final String NORMAL_EXCHANGE = "normal_exchange";
//死信交换机名称
private static final String DEAD_EXCHANGE = "dead_exchange";
//普通队列名称
private static final String NORMAL_QUEUE = "normal_queue";
//死信队列名称
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//声明死信交换机和普通交换机,类型为direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明普通队列,消息成为死信之后发送到死信交换机,发送到死信队列
//最后一个参数:Map arguments
Map<String, Object> arguments = new HashMap<>();
//第一种成为死信的原因:过期时间,正常队列过期之后的死信交换机
//过期时间: 10s = 10000ms 单位为ms,也可以在生产者设置过期时间,为了方便,此处在生产者放设置
// arguments.put("x-message-ttl",10000);
//声明消息成为死信之后发送到的队列
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信routingKey:lisi
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");
//接收消息
channel.basicConsume(NORMAL_QUEUE,true,RabbitMqUtils.getDeliverCallback("Consume1"),RabbitMqUtils.getCancelCallback());
}
}
演示:
可以看到生产者有10条消息,稍等之后刷新
因为无法处理,所以进入了死信队列
package com.example.demo08;
import com.example.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author 我见青山多妩媚
* @date Create on 2022/8/20 21:33
* 死信队列实战:消费者2
*/
public class Consumer02 {
//普通交换机名称
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
System.out.println("Consumer02等待接收消息");
//接收消息
channel.basicConsume(DEAD_QUEUE,true,RabbitMqUtils.getDeliverCallback("Consumer02"),RabbitMqUtils.getCancelCallback());
}
}
因为消费者2仅仅用来消费死信队列的内容,所以代码比较简单。
//运行结果
Consumer02等待接收消息
Consumer02接收到的消息:info:1
Consumer02接收到的消息:info:2
Consumer02接收到的消息:info:3
Consumer02接收到的消息:info:4
Consumer02接收到的消息:info:5
Consumer02接收到的消息:info:6
Consumer02接收到的消息:info:7
Consumer02接收到的消息:info:8
Consumer02接收到的消息:info:9
Consumer02接收到的消息:info:10
此时dead_queue也空了
2、队列达到最大长度
package com.example.demo08;
import com.example.utils.RabbitMqUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author 我见青山多妩媚
* @date Create on 2022/8/20 21:59
* 死信队列之生产者
*/
public class Producer {
//普通队列名称
private static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//死信消息 设置TTL(time to live)时间
for (int i = 0; i < 10; i++) {
String message = "info:"+(i+1);
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}
Map<String, Object> arguments = new HashMap<>();
//第一种成为死信的原因:过期时间,正常队列过期之后的死信交换机
//过期时间: 10s = 10000ms 单位为ms,也可以在生产者设置过期时间,为了方便,此处在生产者放设置
// arguments.put("x-message-ttl",10000);
//声明消息成为死信之后发送到的队列
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信routingKey:lisi
arguments.put("x-dead-letter-routing-key","lisi");
//设置最大队列长度:6,普通队列长度比6大时,进入死信队列
arguments.put("x-max-length",6);
四条消息进入死信队列,剩下的就和上面的一样,开启消费者2消费掉其中的数据
Consumer02等待接收消息
Consumer02接收到的消息:info:1
Consumer02接收到的消息:info:2
Consumer02接收到的消息:info:3
Consumer02接收到的消息:info:4
3、消息被拒绝
为了不被上方的影响,将Consumer01启动,消费掉6个消息
Consume1接收到的消息:info:5
Consume1接收到的消息:info:6
Consume1接收到的消息:info:7
Consume1接收到的消息:info:8
Consume1接收到的消息:info:9
Consume1接收到的消息:info:10
自定义拒绝消息,拒绝info:5
package com.example.demo08;
import com.example.utils.RabbitMqUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @author 我见青山多妩媚
* @date Create on 2022/8/20 21:33
* 死信队列实战:消费者1
*/
public class Consumer01 {
//普通交换机
private static final String NORMAL_EXCHANGE = "normal_exchange";
//死信交换机名称
private static final String DEAD_EXCHANGE = "dead_exchange";
//普通队列名称
private static final String NORMAL_QUEUE = "normal_queue";
//死信队列名称
private static final String DEAD_QUEUE = "dead_queue";
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = RabbitMqUtils.getChannel();
//声明死信交换机和普通交换机,类型为direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明普通队列,消息成为死信之后发送到死信交换机,发送到死信队列
//最后一个参数:Map arguments
Map<String, Object> arguments = new HashMap<>();
//第一种成为死信的原因:过期时间,正常队列过期之后的死信交换机
//过期时间: 10s = 10000ms 单位为ms,也可以在生产者设置过期时间,为了方便,此处在生产者放设置
// arguments.put("x-message-ttl",10000);
//声明消息成为死信之后发送到的队列
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信routingKey:lisi
arguments.put("x-dead-letter-routing-key","lisi");
//设置最大队列长度:6,普通队列长度比6大时,进入死信队列
// 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());
if(msg.equals("info:5")){
System.out.println("Consume1拒收"+msg);
//拒绝消息,消息的标签;是否重新放回队列:false不放回普通队列-->成为死信队列
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}else {
//打印消息内容
System.out.println("Consume1接收到的消息:"+new String(message.getBody()));
//手动应答消息
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
};
//接收消息,开启手动应答,不然无法拒绝消息
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,RabbitMqUtils.getCancelCallback());
}
}
死信队列中有一条消息
Consume1接收到的消息:info:1
Consume1接收到的消息:info:2
Consume1接收到的消息:info:3
Consume1接收到的消息:info:4
Consume1拒收info:5
Consume1接收到的消息:info:6
Consume1接收到的消息:info:7
Consume1接收到的消息:info:8
Consume1接收到的消息:info:9
Consume1接收到的消息:info:10
Consumer02等待接收消息
Consumer02接收到的消息:info:5