1.TTL
什么是TTL
概念介绍
TTL全称Time to live (存活时间/过期时间)
当消息到达存活时间之后,还没有被消费,会被自动清除
RabbitMQ可以对消息设置过期时间,也可以对整个队列设置过期时间
应用场景
比如我们日常生活中在网上购买东西他会有一个规定的付款时间,这个时间过了,这个订单就被取消了,即订单系统将订单提交带中间件中,自付系统如果没有在规定时间内取出该订单,该订单就被取消了
RabbitMQ控制台演示
创建一个队列,队列中消息过期时间为1000毫秒
绑定交换机
发布一条消息
可以发现队列中多了一条消息
十秒钟后
代码演示
我这里已经集成了Spring框架
Spring配置文件中的配置这里是设置队列的过期时间
<!--ttl-->
<!--创建一个队列-->
<rabbit:queue id="test_queue_ttl5" name="test_queue_ttl5">
<rabbit:queue-arguments>
<!--x-message-ttl指定队列中消息的保存时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<!--设置queue参数-->
<!--绑定队列与交换机-->
<rabbit:topic-exchange name="test_exchange_ttl2">
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="test_queue_ttl5"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
设置消息的过期时间
@Test
public void test7(){
/*消息后处理对象,设置一些参数的消息信息*/
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message消息的过期时间
message.getMessageProperties().setExpiration("5000");
/*返回消息*/
return message;
}
};
rabbitTemplate.convertAndSend("test_exchange_ttl2","ttl.hh","This is message2!!",messagePostProcessor);
}
测试函数代码
@Test
public void test6(){
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("test_exchange_ttl2","ttl.message","This is message!!");
}
}
十秒后
小结:
设置队列过期时间使用参数:x-message-ttl ,单位:ms毫秒,会对整个队列消息统一过期。
如果设置消息过期时间使用参数:expiration单位ms(毫秒),当该消息在队列头部时(消费时),会单独判断这个消息是否过期
如果两者都进行设置以时间短的为主
注意事项
如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。队列过期后,会将队列中的所有消息清除,消息过期后,只有消息在队列顶端,才会判断其是否过期
2.死信队列
什么是死信队列
死信队列,英文缩写DLX,Dead Letter Exchange(死信交换机) ,当消息成为Dead Message后,可以被重新发送到另一个交换机,这个交换机就是DLX
消息成为死信的三种情况:
1.队列消息长度到达限制
2.消费者拒绝接受消息,basicNack/basicReject,并且不把消息重新放入原目标队列, requeue=false;
3.原队列存在过期设置,消息到达超时时间未被消费
队列绑定死信交换机
给队列设置参数:x-dead-letter-exchange和x-dead-letter-routing-key
案例演示
演示步骤
1.创建正常的队列与交换机
2.创建死信队列与死信交换机
3.将正常队列与死信交换机绑定
4.测试消息成为死信消息的三种情况
1.创建正常的队列与交换机
<rabbit:queue id="normal_queue_rabbit" name="normal_queue_rabbit"/>
//绑定队列与交换机
<rabbit:direct-exchange name="normal_exchange_rabbit">
<rabbit:bindings>
<rabbit:binding queue="normal_queue_rabbit" key="normal"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
2.创建死信队列与死信交换机
<!--声明死信队列-->
<rabbit:queue id="dead_queue_rabbit" name="dead_queue_rabbit"/>
<!--声明死信交换机-->
<rabbit:topic-exchange name="dead_exchange_rabbit">
<rabbit:bindings>
<rabbit:binding pattern="dead.#" queue="dead_queue_rabbit"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
3.将正常队列与死信交换机绑定
<!--声明正常的队列-->
<rabbit:queue id="normal_queue_rabbit" name="normal_queue_rabbit">
<!--正常队列绑定死信交换机-->
<rabbit:queue-arguments>
<!--x-dead-letter-exchange,死信交换机名称-->
<entry key="x-dead-letter-exchange" value="dead_exchange_rabbit"></entry>
<!--x-dead-letter-routing-key,路由键名称-->
<entry key="x-dead-letter-routing-key" value="dead.one"/>
<!--设置队列的过期时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<!--设置队列长度-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
4.测试消息成为死信消息的三种情况
4.1.队列长度受到限制
@Test
public void test8(){
/*发送10条以上消息*/
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("normal_exchange_rabbit","normal","This is message!!"+i);
}
}
4.2.消费者拒接消费消息,并且不重回队列
package com.pjh.listen;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
/*@Component*/
public class ACKListenerTwo implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println(deliveryTag);
Thread.sleep(1000);
try{
/*接受装换消息*/
System.out.println(new String(message.getBody()));
/*处理业务逻辑*/
System.out.println("处理业务逻辑");
int i=3/0;
/*手动签收*/
channel.basicAck(deliveryTag,true);
}catch (Exception e){
/*拒绝签收*/
/*
* 第三个参数 requeue:重回队列 这里需要设置未false才回去到死信队列中
* broker会重新发送该消息给消费端
* */
System.out.println("出错了!!");
channel.basicNack(deliveryTag,true,true);
}
}
}
4.3.原队列存在消息过期设置,消息到达超时时间未被消费
@Test
public void test8(){
/*消息过期发送到死信队列*/
rabbitTemplate.convertAndSend("normal_exchange_rabbit","normal","This is message!!");
}
应用场景
场景1
- 定位问题:例如,某条消息被多次消费后却未被删除,一般是由于该消息未被正确消费,可能存在问题需要回溯定位。 您可以设置最大接收次数,超额后该消息会被淘汰到指定的死信队列,便于后续问题发现。
- 优先级队列:例如,摩拜单车等 O2O 客户,对访问时延、实时性要求非常高。单车开锁逻辑中,当 CMQ 堆积了1亿条消息时,会优先处理最新生产的部分消息,老的消息淘汰到死信队列,在消费者有能力时再处理。老的消息(如用户扫码开车场景)在等待时已流失了用户,老的消息的价值会偏小,建议优先处理最新消息。
场景2
场景介绍:我们都经常在淘宝上买东西,当我们提交订单后,如果某个时间段之内我们没有支付,淘宝肯定不会帮我们一直保留那个订单,如果超过半个小时我们未支付的话,淘宝会自动帮我们取消订单。在没有用RabbitMQ消息队列之前,我们可以通过设置一个定时任务,设定一个定时规则去轮询数据库查询超过半个小时而且未支付的订单,然后修改订单状态为已取消,这也是一个解决方案,但是需要轮询数据库,增加了对数据库的压力。
在学习了死信队列之后,其实这种场景可以使用死信队列来做,就是用户提交订单之后,发送一条消息并且设置消息过期时间为半个小时(或其他时间),如果超过设置的这个时间,那么消息自动变成死信,就会被转发到死信队列中,这时候我们可以监听死信队列中的消息_,_然后查询一下订单的状态,如果还是未支付的话,那么更新订单的状态为已取消。
死信队列小结
1.死信交换机和死信队列和普通的没有区别
2.当消息成为死信后,如果该队列绑定死信交换机,则消息会被死信交换机重新路由到死信队列
3.消息成为死信的三种情况:
1.队列长度受到限制
2.消费者拒接消费消息,并且不重回队列
3.原队列存在消息过期设置,消息到达超时时间未被消费
3.延迟队列
什么是延迟队列
延迟队列,即消息进入队列之后不会立即被消费,只有到达指定时间后,才会被消费
应用场景
场景1
用户在淘宝下单后如果30分钟内未支付,则系统自动取消订单,将订单回滚到数据库
场景2
新用户注册成功7天后,发短信问候
解决方案1
在没有用RabbitMQ消息队列之前,我们可以通过设置一个定时任务,设定一个定时规则去轮询数据库查询超过半个小时而且未支付的订单,然后修改订单状态为已取消,这也是一个解决方案,但是需要轮询数据库,增加了对数据库的压力。
解决方案2
使用延迟队列
读者可能注意到了,我前面在将死信队列的时候也讲到了这个案例
延迟队列实现方式
概述
RabbitMQ中没有提供延迟队列的功能
虽然RabbitMQ没有提供延迟队列的功能,但是我们可以使用 TTL+死信队列组合实现延迟队列的效果,这个我在前面死信队列那里也讲过一点
图解
案例演示
演示步骤
1.创建一个普通队列与一个普通交换机,绑定二者
2.创建一个死信队列与死信交换机,绑定二者
3.绑定普通队列与死信交换机
4.创建一个监听器类
1.创建一个普通队列与一个普通交换机,绑定二者
<rabbit:queue id="order_queue_normal" name="order_queue_normal"/>
<!--创建普通交换机-->
<rabbit:direct-exchange name="order_exchange_normal">
<rabbit:bindings>
<rabbit:binding queue="order_queue_normal" key="normalOrder"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
2.创建一个死信队列与死信交换机,绑定二者
<!--创建死信队列-->
<rabbit:queue id="order_queue_dead" name="order_queue_dead"/>
<!--创建死信交换机-->
<rabbit:topic-exchange name="order_exchange_dead">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue_dead"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
3.绑定普通队列与死信交换机
<rabbit:queue id="order_queue_normal" name="order_queue_normal">
<rabbit:queue-arguments>
<!--x-dead-letter-exchange,死信交换机名称-->
<entry key="x-dead-letter-exchange" value="order_exchange_dead"></entry>
<!--x-dead-letter-routing-key,路由键名称-->
<entry key="x-dead-letter-routing-key" value="order.banana"/>
<!--设置队列的过期时间-->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<!--设置队列长度-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
4.创建一个监听器类
package com.pjh.listen;
import com.rabbitmq.client.Channel;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;
@Component
public class TTLListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try{
System.out.println("接受到的消息为:");
System.out.println(new String(message.getBody()));
System.out.println("订单未支付,回滚到数据库");
/*确认签收*/
channel.basicAck(deliveryTag,true);
}catch (Exception e){
System.out.println("出错了");
/*拒绝签收*/
/*第三个参数为是否重新回到队列*/
channel.basicNack(deliveryTag,true,false);
}
}
}
5.在配置文件中配置监听器监听的队列
<rabbit:listener-container connection-factory="connectionFactory" prefetch="1"
acknowledge="manual" >
<!--ref监听器类名 queue-names 监听队列的名称-->
<rabbit:listener ref="TTLListener" queue-names="order_queue_dead"/>
</rabbit:listener-container>
6.测试
开始两个队列均为空
启动消费者端的监听类
生产者端向order_queue_normal队列发送消息
十秒钟后
消费者端拿到order_queue_dead中的数据,控制台输出如下
延迟队列小结
1.延迟队列是指消息进入队列之后,可以被延迟一定时间,再进行消费
2.RabbitMQ没有提供延迟队列功能,但是可以使用:TTL+DLX(死信队列)来实现延迟队列的效果
7.日志与监控
RabbitMQ默认日志存放路径: /var/log/rabbitmq/[email protected]
日志包含了RabbitMQ的版本号,Erlang的版本号,RabbitMQ服务节点名称。Cookie的hash值,
RabbitMQ配置文件地址,内存限制,磁盘限制,默认账户guest的创建以及权限配置