应用结构
应用分基本和高级两部分, 从配置文件到消费者到生产者都有做区分, advance:高级, base:基础
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mrathena.middle.ware</groupId>
<artifactId>rabbit.mq.spring</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
rabbitmq.properties
rabbitmq.host=116.62.162.48
rabbitmq.port=5672
rabbitmq.username=mrathena
rabbitmq.password=password
rabbitmq.virtual-host=spring
基础
生产者配置 spring-rabbitmq-producer.xml
<?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"/>
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机. 默认交换机类型为direct,名字为:"",路由键为队列的名称-->
<!--
id:bean的名称
name:queue的名称
auto-declare:自动创建
auto-delete:自动删除。 最后一个消费者和该队列断开连接后,自动删除队列
durable:是否持久化
-->
<!-- 简单模式 -->
<!-- ignore-declaration-exceptions: 忽略队列已存在且声明时属性不一致的报错 -->
<rabbit:queue id="queue-hello-world" name="queue-hello-world" auto-declare="true" durable="false" ignore-declaration-exceptions="true"/>
<!--工作队列模式-->
<rabbit:queue id="queue-work-queue" name="queue-work-queue" auto-declare="true" durable="false"/>
<!-- 发布订阅模式 -->
<!-- fanout: 广播, 所有绑定到交换机的队列都能收到消息 -->
<rabbit:queue id="queue-fanout-1" name="queue-fanout-1" auto-declare="true" durable="false"/>
<rabbit:queue id="queue-fanout-2" name="queue-fanout-2" auto-declare="true" durable="false"/>
<rabbit:fanout-exchange id="exchange-fanout" name="exchange-fanout" auto-declare="true" durable="false">
<rabbit:bindings>
<rabbit:binding queue="queue-fanout-1"/>
<rabbit:binding queue="queue-fanout-2"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!-- 路由模式 -->
<!-- direct: 定向, 绑定到交换机且指定的队列才能收到消息 -->
<rabbit:queue id="queue-direct" name="queue-direct" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-direct" name="exchange-direct">
<rabbit:bindings>
<!--key: 路由键-->
<rabbit:binding queue="queue-direct" key="direct"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 主题模式 -->
<!-- topic: 主题, 绑定到交换机且满足条件的队列才能收到消息, 路由键:*匹配一个单词,#匹配多个单词 -->
<rabbit:queue id="queue-topic-1" name="queue-topic-1" auto-declare="true" durable="false"/>
<rabbit:queue id="queue-topic-2" name="queue-topic-2" auto-declare="true" durable="false"/>
<rabbit:queue id="queue-topic-3" name="queue-topic-3" auto-declare="true" durable="false"/>
<rabbit:topic-exchange id="exchange-topic" name="exchange-topic" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="queue-topic-1" pattern="topic.*"/>
<rabbit:binding queue="queue-topic-2" pattern="topic.#"/>
<rabbit:binding queue="queue-topic-3" pattern="topic.three.*"/>
</rabbit:bindings>
</rabbit:topic-exchange>
<!-- rabbitTemplate-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
生产者测试 ProducerTest
package com.mrathena;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerBaseTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld() {
rabbitTemplate.convertAndSend("queue-hello-world", "hello world");
}
@Test
public void testWorkQueues() {
rabbitTemplate.convertAndSend("queue-work-queue", "work queue");
}
@Test
public void testFanout() {
rabbitTemplate.convertAndSend("exchange-fanout", "", "fanout");
}
@Test
public void testDirect() {
rabbitTemplate.convertAndSend("exchange-direct", "direct", "direct");
}
@Test
public void testTopics() {
rabbitTemplate.convertAndSend("exchange-topic", "topic.a", "topic topic.a");
rabbitTemplate.convertAndSend("exchange-topic", "topic.a.b", "topic topic.a.b");
rabbitTemplate.convertAndSend("exchange-topic", "topic.three.a.b", "topic topic.three.a.b");
}
}
消费者配置 spring-rabbitmq-consumer.xml
<?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"/>
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--基础部分-->
<bean id="queueHelloWorldListener" class="com.mrathena.rabbit.mq.listener.base.QueueHelloWorldListener"/>
<bean id="queueWorkQueueOneListener" class="com.mrathena.rabbit.mq.listener.base.QueueWorkQueueOneListener"/>
<bean id="queueWorkQueueTwoListener" class="com.mrathena.rabbit.mq.listener.base.QueueWorkQueueTwoListener"/>
<bean id="queueFanoutListener" class="com.mrathena.rabbit.mq.listener.base.QueueFanoutListener"/>
<bean id="queueDirectListener" class="com.mrathena.rabbit.mq.listener.base.QueueDirectListener"/>
<bean id="queueTopicOneListener" class="com.mrathena.rabbit.mq.listener.base.QueueTopicOneListener"/>
<bean id="queueTopicTwoListener" class="com.mrathena.rabbit.mq.listener.base.QueueTopicTwoListener"/>
<bean id="queueTopicThreeListener" class="com.mrathena.rabbit.mq.listener.base.QueueTopicThreeListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="queueHelloWorldListener" queue-names="queue-hello-world"/>
<rabbit:listener ref="queueWorkQueueOneListener" queue-names="queue-work-queue"/>
<rabbit:listener ref="queueWorkQueueTwoListener" queue-names="queue-work-queue"/>
<rabbit:listener ref="queueFanoutListener" queue-names="queue-fanout-1,queue-fanout-2"/>
<rabbit:listener ref="queueDirectListener" queue-names="queue-direct"/>
<rabbit:listener ref="queueTopicOneListener" queue-names="queue-topic-1"/>
<rabbit:listener ref="queueTopicTwoListener" queue-names="queue-topic-2"/>
<rabbit:listener ref="queueTopicThreeListener" queue-names="queue-topic-3"/>
</rabbit:listener-container>
</beans>
消费者测试 QueueHelloWorldListener
package com.mrathena.rabbit.mq.listener.base;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
@Slf4j
public class QueueHelloWorldListener implements MessageListener {
@Override
public void onMessage(Message message) {
log.info("{}", new String(message.getBody()));
}
}
还有一些Listener都是一样的内容, 只是类名不同, 通过日志打印的类名查看区别
com.mrathena.rabbit.mq.listener.base.QueueWorkQueueOneListener
com.mrathena.rabbit.mq.listener.base.QueueWorkQueueTwoListener
com.mrathena.rabbit.mq.listener.base.QueueDirectListener
com.mrathena.rabbit.mq.listener.base.QueueFanoutListener
com.mrathena.rabbit.mq.listener.base.QueueTopicOneListener
com.mrathena.rabbit.mq.listener.base.QueueTopicTwoListener
com.mrathena.rabbit.mq.listener.base.QueueTopicThreeListener
高级特性
生产者 confirm return
生产者配置
<!--publisher-confirms="true"在新版本中被替换成了confirm-type="CORRELATED"-->
<!--spring.rabbitmq.publisher-confirms在新版本中被替换成了spring.rabbitmq.publisher-confirm-type=correlated-->
<rabbit:connection-factory id="connectionFactory"
host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"
confirm-type="CORRELATED"
publisher-returns="true"/>
<!-- 生产者 confirm return -->
<rabbit:queue id="queue-confirm-return" name="queue-confirm-return" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-confirm-return" name="exchange-confirm-return">
<rabbit:bindings>
<rabbit:binding queue="queue-confirm-return" key="confirm"/>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者测试
package com.mrathena.advance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class ProducerConfirmReturnTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testConfirm() {
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("broker 接收消息");
} else {
System.out.println("broker 拒收消息: " + cause);
}
}
});
rabbitTemplate.convertAndSend("exchange-confirm-return", "confirm", "direct");
// 消息从生产者到不了交换机的会被拒收
rabbitTemplate.convertAndSend("exchange-confirm-return-not-exist", "confirm", "direct");
rabbitTemplate.convertAndSend("exchange-confirm-return", "confirm.not.exist", "direct");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testReturn() {
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
System.out.println("消息被退回: " + returned);
}
});
// 消息从交换机到不了队列的会被退回(这里演示不设置rabbitTemplate.setMandatory(true);的情况)
rabbitTemplate.convertAndSend("exchange-confirm-return", "confirm.not.exist", "direct");
// 设置交换机处理失败消息的模式 为true的时候,消息达到不了 队列时,会将消息重新返回给生产者
rabbitTemplate.setMandatory(true);
// 消息从交换机到不了队列的会被退回(这里演示设置了rabbitTemplate.setMandatory(true);的情况)
rabbitTemplate.convertAndSend("exchange-confirm-return", "confirm.not.exist", "direct");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费者 ack
消费者配置
<!-- 消费端ack -->
<bean id="ackListener" class="com.mrathena.rabbit.mq.listener.advance.AckListener"/>
<!-- acknowledge="manual":手动签收 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="ackListener" queue-names="queue-ack"/>
</rabbit:listener-container>
消费者监听器
package com.mrathena.rabbit.mq.listener.advance;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import java.util.concurrent.TimeUnit;
@Slf4j
public class AckListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
log.info("{}", new String(message.getBody()));
// 通过修改, 模拟出现异常
// int i = 5 / 0;
int i = 5 / 1;
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
// 拒绝签收
// 第三个参数:requeue:重回队列。如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
// 如果是false, 则消息被丢弃
channel.basicNack(deliveryTag, true, true);
}
}
}
生产者配置
<!-- 消费者ack -->
<rabbit:queue id="queue-ack" name="queue-ack" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-ack" name="exchange-ack">
<rabbit:bindings>
<rabbit:binding queue="queue-ack" key="ack"/>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者测试
package com.mrathena.advance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class CustomerAckTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testAck() {
rabbitTemplate.convertAndSend("exchange-ack", "ack", "direct");
}
}
消费端限流
消费者配置
<!-- 消费端限流 -->
<bean id="qosListener" class="com.mrathena.rabbit.mq.listener.advance.QosListener"/>
<!--定义监听器容器 acknowledge="manual":手动签收 prefetch="1":每次抓取多少条消息 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
<rabbit:listener ref="qosListener" queue-names="queue-qos"/>
</rabbit:listener-container>
消费者监听器
package com.mrathena.rabbit.mq.listener.advance;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import java.util.concurrent.TimeUnit;
@Slf4j
public class QosListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
log.info("{}", new String(message.getBody()));
try {
// 便于观察一次拿几条的效果
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
生产者配置
<!-- 消费端限流 -->
<rabbit:queue id="queue-qos" name="queue-qos" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-qos" name="exchange-qos">
<rabbit:bindings>
<rabbit:binding queue="queue-qos" key="qos"/>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者测试
package com.mrathena.advance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class CustomerQosTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testQos() {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("exchange-qos", "qos", "direct-" + i);
}
}
}
队列和消息 ttl
消费者配置
<!-- 队列和消息ttl -->
<bean id="ttlListener" class="com.mrathena.rabbit.mq.listener.advance.TtlListener"/>
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="ttlListener" queue-names="queue-ttl"/>
</rabbit:listener-container>
消费者监听器
package com.mrathena.rabbit.mq.listener.advance;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TtlListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
log.info("{}", new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
生产者配置
<!-- 消费端限流 -->
<!-- 消息和队列ttl -->
<rabbit:queue id="queue-ttl" name="queue-ttl" auto-declare="true" durable="false">
<rabbit:queue-arguments>
<!-- x-message-ttl指队列的过期时间, 单位:ms(毫秒), 这里设定队列过期时间是10秒 -->
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
</rabbit:queue-arguments>
</rabbit:queue>
生产者测试
package com.mrathena.advance;
import com.mrathena.rabbit.mq.listener.advance.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class QueueAndMessageTtlTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testTtl() {
// 消息后置处理器
// 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期
// 如果两者都进行了设置,以时间短的为准
// 但是我在Web管理页面直接取消息, 发现即使消息的ttl已经过期, 但是仍然在队列里面, 队列过期后才会消失
// 通过启动消费端TtlListener也是一样的结果, 这就很奇怪了
MessagePostProcessor fiveSecondsMessagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().getHeaders().put("expiration", 1000);
return message;
}
};
MessagePostProcessor fifteenSecondsMessagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().getHeaders().put("expiration", 20 * 1000);
return message;
}
};
Object message = "message 1";
rabbitTemplate.convertAndSend("queue-ttl", message, fiveSecondsMessagePostProcessor);
message = "message 2";
rabbitTemplate.convertAndSend("queue-ttl", message, fifteenSecondsMessagePostProcessor);
// 配合启动ttl监听器, 看看能拿到几个消息
}
}
死信队列
消费者配置
<!-- 死信队列 -->
<bean id="deadLetterListener" class="com.mrathena.rabbit.mq.listener.advance.DeadLetterListener"/>
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="deadLetterListener" queue-names="queue-dead-letter"/>
</rabbit:listener-container>
消费者监听器
package com.mrathena.rabbit.mq.listener.advance;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
@Slf4j
public class DeadLetterListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
log.info("{}", new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
生产者配置
<!-- 死信队列 -->
<!-- 其实就是一个普通队列, 只不过是用来接收死信而已 -->
<!-- 死信: 1. 超过队列最大长度的消息, 放不进去了, 就会变成死信. 这里只测试这一种 -->
<!-- 死信: 2. 队列超时还没有被消费的消息, 就会变成死信, 这种其实就是延时队列, 在延时队列里测试 -->
<!-- 死信: 3. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false, 懒得测了 -->
<!-- 测试方式: 给queue-dead-letter添加消费者, 然后一直给queue-max-length-3发送消息, 第四条起, 消费者将受到消息 -->
<rabbit:queue id="queue-max-length-3" name="queue-max-length-3" auto-declare="true" durable="false">
<rabbit:queue-arguments>
<entry key="x-max-length" value="3" value-type="java.lang.Integer"/>
<!-- 该队列中, 如果新消息无法放入, 则把消息扔到exchange-dead-letter交换机, 使用的routingKey是dead.letter -->
<entry key="x-dead-letter-exchange" value="exchange-dead-letter"/>
<entry key="x-dead-letter-routing-key" value="dead.letter"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:queue id="queue-dead-letter" name="queue-dead-letter" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-dead-letter" name="exchange-dead-letter">
<rabbit:bindings>
<rabbit:binding queue="queue-dead-letter" key="dead.letter"/>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者测试
package com.mrathena.advance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class DeadLetterTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testDeadLetter() {
rabbitTemplate.convertAndSend("", "queue-max-length-3", "dead.letter");
}
}
延时队列
消费者配置
<!-- 延时队列 -->
<bean id="delayListener" class="com.mrathena.rabbit.mq.listener.advance.DelayListener"/>
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
<rabbit:listener ref="delayListener" queue-names="queue-delay"/>
</rabbit:listener-container>
消费者监听器
package com.mrathena.rabbit.mq.listener.advance;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
@Slf4j
public class DelayListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
log.info("{}", new String(message.getBody()));
channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
}
}
生产者配置
<!-- 延时队列 -->
<!-- 本质就是ttl队列加死信队列, ttl队列不要消费者, 超时后, 消息自动发到死信队列, 由那边的消费者消费, 转了个弯实现了延时队列的效果 -->
<!-- 测试方法: 给queue-ttl-10-seconds发送消息, 10秒后, queue-delay的消费者受到该消息 -->
<rabbit:queue id="queue-ttl-10-seconds" name="queue-ttl-10-seconds" auto-declare="true" durable="false">
<rabbit:queue-arguments>
<entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
<!-- 该队列中, 如果消息过期也没被消费, 则把消息扔到exchange-delay交换机, 使用的routingKey是delay.letter -->
<entry key="x-dead-letter-exchange" value="exchange-delay"/>
<entry key="x-dead-letter-routing-key" value="delay.letter"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:queue id="queue-delay" name="queue-delay" auto-declare="true" durable="false"/>
<rabbit:direct-exchange id="exchange-delay" name="exchange-delay">
<rabbit:bindings>
<rabbit:binding queue="queue-delay" key="delay.letter"/>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者测试
package com.mrathena.advance;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer-advance.xml")
public class DelayTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testDelay() {
rabbitTemplate.convertAndSend("", "queue-ttl-10-seconds", "delay - " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
}