目录
1.过期时间 TTL
TTL:time to live(存活时间/过期时间)
当消息到达存活时间后,还没有被消费,就会被自动清除;
过期时间可以对整队列设置,也可以对消息单独进行设置(如果同时设置了队列和消息的过期时间,以时间短的为准,队列过期时间到了之后,会将消息都移除 消息到达过期时间之后,只有在队列顶端的消息,才会判断是否需要移除(提高时间过期的效率))
配置:
队列的过期时间设为15s
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:/rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true" />
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemple对象-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--ttl-->
<rabbit:queue id="spring_queue_ttl" name="spring_queue_ttl" auto-delete="true">
<!--设置queue的参数-->
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="spring_ttl_exchange" id="spring_ttl_exchange" >
<rabbit:bindings>
<rabbit:binding pattern="ttl.#" queue="spring_queue_ttl"/>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
生产者代码:
将消息的过期时间设为5s
/**
* ttl 过期时间
* 1.队列统一过期
* 2.消息单独过期
*
* 如果同时设置了队列和消息的过期时间,以时间短的为准,队列过期时间到了之后,会将消息都移除
*
* 消息到达过其实是那之后,只有在队列顶端的消息,才会判断是否需要移除(提高时间过期的效率)
*/
@Test
public void testTtl(){
/*for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test ");
}*/
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
//消息的后置处理器,设置消息的一些参数信息
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("5000");
return message;
}
};
for (int i = 0; i < 10; i++) {
if(i==5) {
rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test",
messagePostProcessor);
}else{
rabbitTemplate.convertAndSend("spring_ttl_exchange", "ttl.test", "message ttl test ");
}
} });
}
}
运行结果为:
- 如果消息不设置过期时间,消息会在队列中待15s;
- 如果消息设置了过期时间,消息在队列中待了5s后,就被移除了;
- 如果对单个消息设置了过期时间,只有当消息到达过期时间之后,且该消息在队列顶端的消息,才会被移除
2.死信队列/死信交换机
英文Dead Letter Exchange,当消息成为死信消息后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信有三种种方式:
- 消息到达过期时间 TTL;
- 消息数量达到最大限制;
- 消费者拒收消息,使用channel.basicNack/basicReject,并且不把消息重新放回原队列,即requeue=false.
我们来实践一下:
生产端队列以及交换机的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:/rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true" />
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemple对象-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--死信队列 -->
<!--1声明正常的队列 spring_queue_dlx 和交换机 spring_exchange_dlx-->
<!--2声明死信队列queue_dlx和死信交换机exchange_dlx-->
<!--3正常队列绑定死信交换机
设置两个参数:x-dead-letter-exchange:死信交换机名称
x-dead-letter-routing-key:发送给死信交换机的路由key
-->
<!--4消息如何成为死信? ①消息过期 ②消息数量到达最大限度③消费者拒收消息-->
<!--1-->
<rabbit:queue id="spring_queue_dlx" name="spring_queue_dlx" auto-delete="true">
<!--3-->
<rabbit:queue-arguments>
<!--3.1 设置x-dead-letter-exchange-->
<entry key="x-dead-letter-exchange" value="exchange_dlx"></entry>
<!--3.1 设置x-dead-letter-routing-key-->
<entry key="x-dead-letter-routing-key" value="dlx.test"></entry>
<!--4.1设置消息的过期时间-->
<entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
<!--4.2设置队列的长度-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange name="spring_exchange_dlx" id="spring_exchange_dlx" >
<rabbit:bindings>
<rabbit:binding queue="spring_queue_dlx" pattern="test.dlx.#"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--2-->
<rabbit:queue id="queue_dlx" name="queue_dlx" auto-delete="true"/>
<rabbit:topic-exchange name="exchange_dlx" id="exchange_dlx" >
<rabbit:bindings>
<rabbit:binding queue="queue_dlx" pattern="dlx.#"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
测试:
2.1过期消息
消息的过期时间我们设置了15s
<!--4.1设置消息的过期时间-->
<entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
@Test
public void testDlx(){
//1.测试过期消息
rabbitTemplate.convertAndSend("spring_exchange_dlx", "test.dlx.test", "message dlx test ");
}
发送消息后,可见,正常队列spring_queue_dlx接收到一条消息,死信队列queue_dlx暂时无数据
15s后,正常队列里的消息达到过期时间,消息被发送到死信交换机:
2.2 消息数量达到最大限制
正常的消息队列最大数量我们设置了10条
<!--4.2设置队列的长度-->
<entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
@Test
public void testDlx(){
//2.测试消息最大数量
for (int i = 0; i < 20; i++) {
rabbitTemplate.convertAndSend("spring_exchange_dlx", "test.dlx.test", "message dlx test ");
}
}
发送消息后,15s内正常队列里面应该有10条数据,死信队列里面应该有11条数据(2.1中的测试导致死信队列中已有一条数据)
15s后,正常队列里没有消息,死信队列中应该有21条数据:
2.3 消费者拒收消息
消费者配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id = "DlxListener" class="com.cjian.rabbitmq.spring_rabbit.DlxListener"/>
<!--绑定监听器与队列的关系-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="5">
<rabbit:listener ref="DlxListener" queue-names="spring_queue_dlx"/>
</rabbit:listener-container>
</beans>
消费者代码:
package com.cjian.rabbitmq.spring_rabbit;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import java.util.Date;
/**
* @description: 消费者拒收消息,让消息进入死信队列
*
* @author: CJ
* @time: 2021/1/27 10:05
*/
public class DlxListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//接收消息
System.out.println(new String(message.getBody()));
//处理业务逻辑
//Thread.sleep(1000);
//手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println("消费消息出现异常,拒绝接收");
//拒绝签收
//requeue参数的意思为是否重回队列
channel.basicNack(deliveryTag,true,false);
//使用这个也可以
//channel.basicReject(deliveryTag,false);
//或者这个
//channel.basicRecover(false);
}
}
}
我们让生产者发送一条消息后,消费者消费出现异常,消息被移到死信队列
3.延迟队列(重点)
消息进入队列后不会立即被消费,只有达到指定时间后才会被消费
很可惜,rabbitmq并没有提供延迟队列的功能,但是经过上面的学习,我们可以利用TTL+DLX去实现
测试:
生产者配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:/rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
publisher-confirms="true"
publisher-returns="true" />
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义rabbitTemple对象-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
<!--延迟队列
1.定义正常的交换机order_exchange和队列 order_queue
2.定义死信交换机order_exchange_dlx和队列order_queue_dlx
3.绑定,设置正常队列的过期时间
-->
<!--1.定义正常的交换机order_exchange和队列 order_queue-->
<rabbit:queue id="order_queue" name ="order_queue">
<!--3.绑定,设置正常队列的过期时间-->
<rabbit:queue-arguments>
<entry key="x-dead-letter-exchange" value="order_exchange_dlx"></entry>
<entry key="x-dead-letter-routing-key" value="order.dlx.cancel"></entry>
<entry key="x-message-ttl" value="15000" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:topic-exchange id="order_exchange" name="order_exchange">
<rabbit:bindings>
<rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<!--2.定义死信交换机order_exchange_dlx和队列order_queue_dlx-->
<rabbit:queue id="order_queue_dlx" name ="order_queue_dlx"></rabbit:queue>
<rabbit:topic-exchange id="order_exchange_dlx" name="order_exchange_dlx">
<rabbit:bindings>
<rabbit:binding pattern="order.dlx.#" queue="order_queue_dlx"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
</beans>
生产者代码:
@Test
public void testDelay(){
rabbitTemplate.convertAndSend("order_exchange", "order.msg", "delay message test ");
System.out.println("消息发送成功:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
消费者配置:
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<bean id = "delayListener" class="com.cjian.rabbitmq.spring_rabbit.DelayListener"/>
<!--绑定监听器与队列的关系-->
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true" acknowledge="manual" prefetch="5">
<rabbit:listener ref="delayListener" queue-names="order_queue_dlx"/>
</rabbit:listener-container>
</beans>
消费者代码:
package com.cjian.rabbitmq.spring_rabbit;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @description: 延迟队列测试
*
* @author: CJ
* @time: 2021/1/27 11:00
*/
public class DelayListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
//接收消息
System.out.println("消息接收成功:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
System.out.println(new String(message.getBody()));
//手动签收
channel.basicAck(deliveryTag,true);
} catch (Exception e) {
System.out.println("消费消息出现异常,拒绝接收");
//拒绝签收
//requeue参数的意思为是否重回队列
channel.basicNack(deliveryTag,true,true);
}
}
}
先让队列和交换机都创建成功(可以启动生产者,不发送任何消息),然后我们启动消费者,以便后面观察:
这里order_queue_dlx死信队列已被消费者绑定:
生产者发送消息:
消息首先进入正常的队列,15s后进入死信队列,然后立即被消费:
消费者消费日志: