RabbitMQ高级特性:死信队列
前言
跟随b站尚硅谷RabbitMQ视频学习:如果有小伙伴们认为不正确的的地方,欢迎大家一起讨论
下面是b站视频链接:
b站尚硅谷rabbitmq的死信队列视频
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是死信队列?
1.什么是死信队列
当一条消息在队列中出现以下三种情况的时候,该消息就会变成一条死信。
- 消息被拒绝(basic.reject / basic.nack),并且requeue = false
- 消息TTL过期
- 队列达到最大长度
当消息在一个队列中变成一个死信之后,如果配置了死信队列,它将被重新publish到死信交换机,死信交换机将死信投递到一个队列上,这个队列就是死信队列。
2.死信队列的工作模式
3.死信队列的工作流程:
生产者发布消息到普通交换机,普通交换机绑定一个普通队列,如果发布的消息在加入队列的时候发生了1.消息被拒绝
、2.消息超时(TTL)
、3.普通队列达到了最大长度
,那么不符合条件的消息就通过正常队列绑定的私信交换机发送到死信交换机,然后再由死信交换机把消息发送到绑定的死信队列,其中正常用户c1消费了普通队列中的消息,死信用户消费了死信队列中的消息。
二、死信队列的实现
1.创建消费者1
消费者1要绑定正常交换机,正常队列,死信交换机,死信队列,是比较麻烦的一个类,所以先把消费者1实现:
引入rabbitmq依赖,创建一个rabbitmq的连接工具:
代码如下(示例):
package utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RabbitMQUtil {
public static Channel getChannel() throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置参数
connectionFactory.setHost("192.168.209.128");//ip:默认值 localhost
connectionFactory.setPort(5672);//端口 默认值 5672
connectionFactory.setVirtualHost("/xyh");//虚拟机 默认值:/
connectionFactory.setUsername("xyh");//用户名
connectionFactory.setPassword("xyh980616");//密码
//3.获取连接 connection
Connection connection = connectionFactory.newConnection();
//4.创建channel
Channel channel = connection.createChannel();
return channel;
}
}
创建Consumer01:
模拟因为TTL实现死信队列的产生
public class Consumer01_DeadQueue {
//普通交换机名称
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 = RabbitMQUtil.getChannel();
//声明普通交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明普通队列
Map<String, Object> arguments = new HashMap<>();
//过期时间
//10s = 10000ms
arguments.put("x-message-ttl",10000);
//正常队列到死信交换机
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信routingkey
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");
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag,message)->{
String s = new String(message.getBody());
System.out.println("Consumer01接受的消息是:"+s);
}
};
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
}
}
启动Consumer01,创建了两个交换机和队列
2.创建Producer
生产者不知道死信队列是怎么产生的,它只需要绑定普通交换机跟普通队列即可
生产者代码如下:
public class Producer_DeadQueue {
//普通交换机名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
//死信消息 设置TTL时间 单位是毫秒
AMQP.BasicProperties properties =
new AMQP.BasicProperties()
.builder().expiration("100000").build();
for (int i = 1;i<11;i++){
String message = "info"+i;
//发布消息
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
}
}
}
模拟TTL死信队列的产生,先挂掉Consumer01,在启动producer,按照流程消息发布到normal_queue里面,一开始normal_queue的Ready里面会有10条数据,然后十秒钟过后这十条数据都会跑到dead_queue里面去。
一开始在normal_queue里面的结果:
过了10秒后的MQ页面:
可见跟工作流程实现的是一样的。
模拟因为队列达到最大长度而产生的死信队列:
Consumer01的代码:
public class Consumer01_DeadQueue {
//普通交换机名称
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 = RabbitMQUtil.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);
//设置死信routingkey
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");
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag,message)->{
String s = new String(message.getBody());
System.out.println("Consumer01接受的消息是:"+s);
};
channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
}
}
因为我们设置了普通队列最大长度为6,那么如果producer发送10条数据,那么死信队列里面会有4条数据,让我们来看看结果:
producer实现:
public class Producer_DeadQueue {
//普通交换机名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtil.getChannel();
//死信消息 设置TTL时间 单位是毫秒
// AMQP.BasicProperties properties =
// new AMQP.BasicProperties()
// .builder().expiration("100000").build();
for (int i = 1;i<11;i++){
String message = "info"+i;
// channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes());
channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes());
}
}
}
先运行Consumer01,创建出两个队列:
在挂掉Consumer01,运行producer,让我们来看看结果:
跟我们预料的结果是一致的。
模拟消息被拒绝产生的死信队列:
Consumer01 的代码:
public class Consumer01_DeadQueue {
//普通交换机名称
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 = RabbitMQUtil.getChannel();
//声明普通交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);
//声明普通队列
Map<String, Object> arguments = new HashMap<>();
//过期时间
//10s = 10000ms
// arguments.put("x-message-ttl",10000);
//正常队列到死信交换机
arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信routingkey
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");
System.out.println("等待接收消息.....");
DeliverCallback deliverCallback = (consumerTag,message)->{
String s = new String(message.getBody());
if (s.equals("info5")){
System.out.println("Consumer01接受的消息是:"+s+"此消息被C1拒绝");
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
}else {
System.out.println("Consumer01接受的消息是:"+s);
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
};
//开启手动应答
channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag->{});
}
}
先运行Consumer01创建出队列:
不挂断
Consumer01,继续运行Producer,结果为:
让我们再看看控制台是否输出符合预期:
让我们再看看MQ控制台里面拿到的dead_queue里面的信息:
恭喜我们完成了死信队列的实现!