1、前言
因朋友提到参考源码的需求,已经更新至github 教程测试源码:https://github.com/lhmyy521125/rabbitmq-test
博主因为这段时间公司项目比较忙,今天总算抽了点时间继续来和大家分享RabbitMQ教程系列,上一篇文章我们详细介绍了 消费端ACK和消费端拒绝,今天我们来讲解一下消息的TTL生存时间也就是过期时间以及什么是死信队列、延迟队列、优先级队列;
2、TTL Time to Live
TTL过期时间,TTL分别有两种情况;
- 基于消息设置过期 - 不知道大家是否还记得博主在讲解教程的第四篇 RabbitMQ的Queue队列和Message详细使用中有一段代码,如下:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) //设置持久化
.expiration("10000") //设置10秒过期
.build();
channel.basicPublish(exchangeName, routingKey , properties , msg.getBytes());
我们可以通过设置消息的 expiration 属性参数来实现每个消息的不同过期时间,除了以上的方式外,还可以采用一下代码设置,上面的效果一致;
AMQP.BasicProperties properties = new AMQP.BasicProperties();
Properties.setDeliveryMode(2);
properties.setExpiration("60000");
channel.basicPublish(exchangeName, routingKey , properties , msg.getBytes());
- 基于队列设置过期 - 从消息开始进入队列开始计算,只要超过了队列配置的过期时间,那么消息会自动被删除;简单的说进入该队列的所有过期时间是相同的;
通过队列属性设置消息TTL的方法是在channel.queueDeclare 方法中加入x-message-ttl 参数实现的,这个参数的单位是毫秒,设置代码如下:
Map<String,Object> argss = new HashMap<String,Object>();
argss.put("x-message-ttl" , 6000);
channel.queueDeclare(queueName , durable , exclusive , autoDelete , argss);
我们用管控台添加队列效果如下:
那么有人会问了,既然消息可以设置过期时间,队列也可以设置过期时间,如果两个都设置了,到底以哪个过期时间为标准呢?其实RabbitMQ是以两个过期时间较小的那个数值为准,比如消息设置的是 5秒,队列设置的是10秒,那么消息会5秒过期;
消息在队列中的生存时间一旦超过设置的TTL值时,就会变成"死信" (Dead Message),它会被重新发送到死信队列;
3、死信队列 DLX
死信队列 Dead-Letter-Exchange 上面我们说了当消息在一个队列中变成死信(dead message) 之后,它能被重新被发送到另一个交换器中,这个交换器就是DLX,绑定DLX的队列就称之为死信队列;
消息变成dead message有以下情况:
- 上文提到的TTL过期时间;
- 消息被拒绝的时候 basicReject 以及 basicNack 最后一个参数 requeue = false;
- 队列到达最大长度
创建死信队列和普通的队列创建基本相同,我们只需要按照之前的创建方式: 创建 Exchange ,创建Queue ,设置RoutingKey 这里不在赘述;
而需要将队列启用死信的转发,需要设置队列的最后一个参数agruments(Map),通过在channel.queueDeclare 方法中设置x-dead-letter-exchange 参数来为这个队列添加DLX
下面用代码来进行演示
消费者:
public class DlxConsumer {
public static void main(String[] args) throws Exception {
//1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.28");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("toher");
connectionFactory.setPassword("toher888");
//2 创建Connection
Connection connection = connectionFactory.newConnection();
//3 创建Channel
Channel channel = connection.createChannel();
//4 创建私信队列
channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "#");
//5 正常声明我们工作的交换机和队列 与DlxProducer exchangeName相同
String exchangeName = "test_dlx_exchange";
String queueName = "test_dlx_queue";
//因为*号代表匹配一个单词
String routingKey = "dlx.*";
//表示声明了一个交换机
channel.exchangeDeclare(exchangeName, "topic", true, false, false, null);
//表示声明了一个队列 创建agruments属性,要设置到声明队列上
Map<String, Object> agruments = new HashMap();
//dlx.exchange 是我们要创建的死信队列
agruments.put("x-dead-letter-exchange", "dlx.exchange");
channel.queueDeclare(queueName, true, false, false, agruments);
//建立一个绑定关系:
channel.queueBind(queueName, exchangeName, routingKey);
//6 创建消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String msg = new String(body, "UTF-8");
//采用 Nack 拒绝 并重回队列
System.out.println("未消费:" + msg);
channel.basicNack(envelope.getDeliveryTag(), false, false);
//采用Reject拒绝
//channel.basicReject(envelope.getDeliveryTag(), false);
}
};
//参数:队列名称、是否自动ACK、Consumer
channel.basicConsume(queueName, false, consumer);
}
}
运行消费者,进入WEB管控台查看
生产者:
public class DlxProducer {
public static void main(String[] args) throws Exception {
//1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.28");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("toher");
connectionFactory.setPassword("toher888");
//2 创建Connection
Connection connection = connectionFactory.newConnection();
//3 创建Channel
Channel channel = connection.createChannel();
//4 指定我们的消息投递模式: 消息的确认模式
channel.confirmSelect();
//5 声明交换机 以及 路由KEY
String exchangeName = "test_dlx_exchange";
//设置routingKey
String routingKey = "dlx.send";
//6 发送一条消息
String msg = "Test DLX Message";
for (int i = 1; i < 5; i++) {
Map<String, Object> headers = new HashMap<String, Object>();
//设置TTL过期时间 10秒
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.expiration("10000")
.headers(headers)
.build();
channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
}
}
}
运行生产者,观察我们的管控台:
在实际工作中死信队列也是我们经常用到的,它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了Basic.Nack 或者Basic.Reject) 而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统;
4、延迟队列
有那么一种情景:生产者在将消息发出后,不希望消费者立马消费,而是需要等待10分钟后再消费;如何来实现?那么就需要使用到延时队列;
从某种意义上说,RabbitMQ并没有直接支持的延迟队列,实际上是通过我们TTL + DLX来实现;
上图可以看出,消费者需要订阅的是死信队列,当queue1队列TTL过期后自动转发到queue2死信队列,从而实现消费者的延时接收;
5、优先级队列
从字面意思上理解,就是消息优先被消费者消费的队列,优先级越高的消息具备优先被消费;实际上是通过设置队列的x - max - priority 参数来实现;
首先我们创建队列的时候,先配置一个队列的最大优先级。之后在需要发送时在消息中设置消息当前的优先级;
//设置队列的最大优先级
Map<String, Object> args = new HashMap<String, Object>() ;
args.put( "x-rnax-priority",10) ;
channel.queueDeclare("queue",true,fa1se,false,args) ;
发送消息设置消息的优先级,与设置TTL一样设置对应属性即可
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2) //设置持久化
.priority(5) ; //设置优先级别为5
.build();
channel.basicPublish(exchangeName,routingKey,properties,msg.getBytes());
6、结语
本章节主要介绍了消息过期TTL设置,以及各种工作中我们常用的一些队列介绍,大家可以参考样例代码进行一一测试,谢谢大家!到本章节为止,RabbitMQ的基础教程已完毕,通过这八篇文章相信大家对RabbitMQ一定有了一定的了解,把每个章节的代码敲一次加深印象,理解其原理!在下次的教程中,我们将介绍如何使用Spring Boot 整合RabbitMQ 进行开发