“芜湖!那岂不是说,此次有幸目睹圣女的风采了”
“在下李兆鸣实名喜欢神若圣女!!!”
“哎哎哎,说话那小子你TM谁啊,老子才叫李兆鸣,你别给老子树敌啊草!”
Spring 整合RabbitMQ(不推荐看)
1、消费者工程和生产者工程
1、创建工程spring-rabbitmq-producer和spring-rabbitmq-consumer,引入依赖和插件
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2、生产者端创建 spring-rabbitmq.xml 整合配置文件,内容如下
指定要连接的MQ服务器,因为是简单模式,就只是创建队列(指定队列名)就好了
<?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: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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="192.168.2.108"
port="5672"
username="admin"
password=""
virtual-host="MyVirtualHost"/>
<!--定义admin组件管理(创建)交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
默认交换机类型为direct,名字为:"",路由键为队列的名称
-->
<!--简单模式,没有交换机,1个队列
name="队列的名字"
id: 队列bean对象的唯一表示
-->
<rabbit:queue id="spring_simple_queue" name="spring_simple_queue" durable="true"
auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<!--定义rabbitTemplate对象(模板对象,封装了大量的API操作)操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>
生产端创建一个测试类:
convert :直译为“转换”
convertAndSend()方法的3个参数分别指:
参数1:交换机名称
参数2:路由键名(广播设置为空)
参数3:发送的消息内容
@ContextConfiguration(locations = "classpath:spring-rabbitmq.xml") @RunWith(SpringJUnit4ClassRunner.class) public class ProducerTest { @Autowired RabbitTemplate rabbitTemplate; @Test public void t1() { String message = "简单模式: hello spring rabbitmq"; //简单模式 路由键与队列同名 rabbitTemplate.convertAndSend("", "spring_simple_queue", message); } }
运行生产端测试方法t1,在rabbitMq管理控制台中可以看到
消费端创建spring-rabbitmq.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: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/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="192.168.2.108"
port="5672"
username="admin"
password="123456"
virtual-host="MyVirtualHost"/>
<bean id="springQueueListener" class="com.atguigu.rabbitmq.listener.SpringQueueListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_simple_queue"/>
</rabbit:listener-container>
</beans>
2、消费端队列监听器
在配置文件中看的出来消费端出现一个很重要的东西 队列监听器
干什么作用的呢,就是在消费端在消费某一个队列的消息时会和一个监听器匹配,消息的内容可由该监听器拿到
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
String msg = new String(message.getBody(), "utf-8");
System.out.printf("接收路由名称为:%s,路由键为:%s,队列名为:%s的消息:%s \n",
message.getMessageProperties().getReceivedExchange(),
message.getMessageProperties().getReceivedRoutingKey(),
message.getMessageProperties().getConsumerQueue(),
msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
消费端创建测试类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath: spring-rabbitmq.xml") public class ConsumerTest { @Test public void test1(){ boolean flag = true; //作为消费方,服务不停 while (flag){ } } }
消费端接收到消息,控制台输出
3、Spring整合-MQ工作模式
生产者端在spring-rabbitmq.xml 加入内容
<!--工作模式 1个队列-->
<rabbit:queue id="spring_work_queue" name="spring_work_queue" durable="true"
auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
在测试类ProducerTest里加测试方法。
convertAndSend()方法的3个参数分别指: /** * 参数1:交换机名称 * 参数2:路由键名(广播设置为空) * 参数3:发送的消息内容 */
@Test
public void t2() {
for (int i = 0; i < 10; i++) {
String message = "工作模式:hello spring rabbitmq---"+i;
rabbitTemplate.convertAndSend("", "spring_work_queue", message);
}
}
在消费者端就更简单了 ,只需要在spring-rabbitmq.xml中配置监听器和队列名的绑定关系。在工作模式里,就是多个消费者监听同一个队列,配置如下:
测试结果
4、Spring整合-其余模式
剩下的3种模式就一起说了吧
convertAndSend()方法的3个参数分别指:
/** * 参数1:交换机名称 * 参数2:路由键名(广播设置为空) * 参数3:发送的消息内容 */
由于这个方法需要的参数,我们可以推论出在,这三种模式下都是需要指定交换机的名字,类型。并且队列和交换机之间是有不同的绑定关系的。定向交换机(路由模式)就用了key作为路由key。而通配符交换机使用pattern作为路由key。
<!--广播交换机 fanout 一个交换机两个队列 队列和交换机之间的关系 : 绑定 -->
<rabbit:queue id="spring_fanout_queue1" name="spring_fanout_queue1" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:queue id="spring_fanout_queue2" name="spring_fanout_queue2" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:fanout-exchange name="spring_fanout_ex" id="fanoutEx" auto-declare="true" auto-delete="false" durable="true">
<!--绑定-->
<rabbit:bindings>
<rabbit:binding queue="spring_fanout_queue1"></rabbit:binding>
<rabbit:binding queue="spring_fanout_queue2"></rabbit:binding>
</rabbit:bindings>
</rabbit:fanout-exchange>
<!--定向交换机 direct 一个交换机 两个队列 有绑定关系,且指定绑定条件 routingkey-->
<rabbit:queue id="spring_direct_queue1" name="spring_direct_queue1" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:queue id="spring_direct_queue2" name="spring_direct_queue2" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:direct-exchange name="spring_direct_ex" durable="true" auto-declare="true" auto-delete="false">
<rabbit:bindings>
<rabbit:binding queue="spring_direct_queue1" key="springmq"></rabbit:binding>
<rabbit:binding queue="spring_direct_queue2" key="springmq123"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- topic 通配符交换机 一个交换机两个队列 实现绑定关系 且 指定条件 注意: 条件支持通配符 # *-->
<rabbit:queue id="spring_topic_queue1" name="spring_topic_queue1" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:queue id="spring_topic_queue2" name="spring_topic_queue2" durable="true" auto-delete="false" auto-declare="true" exclusive="false"></rabbit:queue>
<rabbit:topic-exchange name="spring_topic_ex" durable="true" auto-declare="true" auto-delete="false">
<rabbit:bindings>
<!--pattern 路由key -->
<rabbit:binding pattern="topic.*" queue="spring_topic_queue1"></rabbit:binding>
<rabbit:binding pattern="topic.#" queue="spring_topic_queue2"></rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
对应的测试方法里
@Test
public void t3() {
String message = "发布订阅模式(广播): hello spring rabbitmq";
/**
* 参数1:交换机名称
* 参数2:路由键名(广播设置为空)
* 参数3:发送的消息内容
*/
rabbitTemplate.convertAndSend("spring_fanout_ex", "", message);
}
@Test
public void t4() {
String message = "路由模式: hello spring rabbitmq";
rabbitTemplate.convertAndSend("spring_direct_ex", "springmq", message);
}
@Test
public void t5() {
/**
* 参数1:交换机名称
* 参数2:路由键名
* 参数3:发送的消息内容
*/
String message = "通配符模式: hello spring rabbitmq";
rabbitTemplate.convertAndSend("spring_topic_ex", "topic.a", message);
}
在消费端,就不写了。累了,毁灭吧
SpringBoot整合RabbitMQ(重要)
消息生产者工程
1、生产者工程依赖、配置文件
创建工程producer-springboot并引入依赖
<!--
1. 父工程依赖
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!--2. rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建配置文件application.yml
spring:
rabbitmq:
host: 192.168.2.108
port: 5672
virtual-host: /xiaoyumao
username: xiaoyumao
password: xiaoyumao
#生产者参数
#开启生产者确认回调
publisher-confirm-type: simple # SIMPLE-同步确认(阻塞) CORRELATED-异步确认
publisher-returns: true # 确认消息是否到达队列
2、配置类RabbitMQConfig
在配置类中使用@Bean的方式配置创建交换机、队列,绑定关系。
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_ex";
public static final String QUEUE_NAME = "boot_queue";
//1.创建交换机
@Bean("bootExchange")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME) //交换机名
.durable(false)//是否持久化
.ignoreDeclarationExceptions()//是否忽略重复定义声明该交换机的异常
.build();
}
//2.创建队列
@Bean("bootQueue")
public Queue bootQueue() {
return QueueBuilder
.nonDurable(QUEUE_NAME) //不持久化并指定队列名称
.build();
}
//3. 队列和交换机绑定关系 Binding
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue bootQueue,
@Qualifier("bootExchange") Exchange bootExchange) {
return BindingBuilder
.bind(bootQueue)
.to(bootExchange)
.with("boot.#")//路由key
.noargs();//noargs():表示不指定参数
}
}
bindQueueExchange方法参数入参:默认使用autoWired装配。(先类型再名字),但是队列不可能只有一个
其实只要注意,还是可以不用写@Bean()里的value属性,也可以不写@Qualifier的,还保证错不了,就是直接把参数名直接复制过来。。省力还错不了
3、生产者—投递消息
生产端直接注入RabbitTemplate完成消息发送,
是因为 springboot自动配置,会给RabbitTemplate初始化并注入容器中。。
@SpringBootTest(classes = RabbitMQApplication.class) //为测试提供上下文环境,class指定启动类 public class MqTest { @Autowired private RabbitTemplate rabbitTemplate;//发送mq消息的客户端 @Test //声明需测试的方法 public void testSend(){ //参数1:交换机名称 参数2:路由key 参数3:消息内容 rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.boot","mq hello"); } }
1.生产者发送消息不关心发送给哪个队列,但是发送消息前必须先初始化交换机,队列,和他们的绑定关系。
2.生产者发消息的时候路由key是写死的,配置类RabbitMQConfig队列绑定交换机的时候才使用通配符。
4、生产者—确认回调配置
被@PostConstruct注解的方法,在对象加载完依赖注入后执行。通常都是一些初始化的操作,但初始化可能依赖于注入的其他组件,所以要等依赖全部加载完
继续在RabbitMQConfig这个配置类里,添加生产者确认回调配置,内容如下:
@Configuration public class RabbitMQConfig { @Autowired RabbitTemplate rabbitTemplate; //生产者确认回调配置 @PostConstruct //标注的方法会在构造器执行后立即调用一次 public void init(){ rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{ // ack: true代表消息到达交换机,false代表消息没有到达交换机 System.out.println("ConfirmCallback ack: " + ack ); System.out.println("ConfirmCallback cause: " + cause); }); /* 消息没有到达队列时回调: */ rabbitTemplate.setReturnCallback((message,replyCode,replyText, exchange,routingKey)->{ System.out.println("ReturnCallback replyCode: "+ replyCode); System.out.println("ReturnCallback replyText: "+ replyText); System.out.println("ReturnCallback exchange: "+ exchange); System.out.println("ReturnCallback routingKey: "+ routingKey); /* 消息没有到达队列 重新投递消息,再次失败的消息可以存到日志中 或者redis缓存 */ }); } }
消息消费者工程
1、消费者工程依赖、配置文件
创建消费者工程consumber-springboot,引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<dependencies>
<!--RabbitMQ 启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
创建application.yml
spring:
rabbitmq:
host: 192.168.2.108
port: 5672
virtual-host: /xiaoyumao
username: xiaoyumao
password: xiaoyumao
listener:
type: simple # simple-listener容器使用一个额外线程处理消息 direct-listener(监听器)容器直接使用consumer线程
simple:
acknowledge-mode: manual # manual-手动 auto-自动(无异常直接确认,有异常无限重试) none-不重试
prefetch: 1 # 配置的值代表一次接收消息的数量
concurrency: 3 # 避免消息堆积,初始化多个消费者线程
2、创建监听器 @RabbitListener
消费端直接使用 @RabbitListener 完成消息接收
@RabbitListener标注的一个方法就是mq的一个消费者
import com.rabbitmq.client.Channel; @Component public class RabbitmqListener { @RabbitListener(queues = { //消费已存在的队列 中的消息 "boot_queue" }) public void listener(Message message , String msg , Channel channel) { System.out.println(msg); } }
启动主程序类MqConsumerApplication
@SpringBootApplication public class MqConsumerApplication { public static void main(String[] args) { SpringApplication.run(MqConsumerApplication.class, args); } }
控制台就可以自动消费boot_queue这个队列的消息了
但是由于我们在配置文件配置的手动确认,所以队列boot_queue中消息还在,由ready变为unacked。
3、消费消息过程发生异常?
默认情况(auto)下,消息者消费消息过程发生异常,怎么处理
spring.rabbitmq.listener.simple.acknowledge-mode=auto
消息消费成功自动ack,
如果有异常消费失败不会ack,但是会无限重试确认消费消息
如果消费异常的消费者停止,消息又变成可以消费的状态
处理异常的正确的做法是acknowledge-mode=manual
1.配置文件中设置为手动确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
2.合理使用try...catch...
@Component
public class RabbitmqListener {
@RabbitListener(queues = { //消费已存在的队列 中的消息
"boot_queue"
})
public void listener(Message message , String msg , Channel channel) throws IOException {
try {
System.out.println(msg);
int i = 1/0;
//手动确认消息 参数2:不批量确认消息的消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
if(message.getMessageProperties().isRedelivered()){
//如果消息重复消费后仍消费失败 丢弃消息
//如果没有绑定死信队列 消息直接丢弃,如果绑定了死信队列 消息会被路由到死信交换机,进而丢弃到死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}else{
//业务执行有异常 消息没能正常消费
//第一次消费出现异常可以拒绝确认消息的消费:让消息重新归队
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false , true);
}
}
}
}
4、注解创建:交换机、队列和绑定关系
方式一:通过配置类创建交换机、队列和他们的绑定关系,上面在生产者工程中RabbitMQConfig已经用到了,不再说。
方式二:通过注解创建交换机、队列和他们的绑定关系
//通过注解创建交换机队列 绑定
@RabbitListener(bindings = {
@QueueBinding(
exchange = @Exchange(
name = "test2.exchange", //交换机名
durable = "true", //持久化
type = ExchangeTypes.TOPIC, //类型
ignoreDeclarationExceptions = "true" //异常忽略
),
value = @Queue(
name = "test2.queue",
durable = "true"
),
key = "test2.#" //路由key
)
})
public void listener(Message message , String msg , Channel channel) throws IOException {
try {
System.out.println("接收到消息: "+msg);
System.out.println(new String(message.getBody()));
//手动确认消息 参数2:不批量确认消息的消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
if(message.getMessageProperties().isRedelivered()){
//如果消息重复消费后仍消费失败 丢弃消息
//如果没有绑定死信队列 消息直接丢弃,如果绑定了死信队列 消息会被路由到死信交换机,进而丢弃到死信队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}else{
//业务执行有异常 消息没能正常消费
//第一次消费出现异常可以拒绝确认消息的消费:让消息重新归队
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false , true);
}
}
}
@RabbitListener
:方法上的注解,声明这个方法是一个消费者方法,需要指定下面的属性:
bindings
:指定绑定关系,可以有多个。值是@QueueBinding
的数组。
@QueueBinding
包含下面属性:
value
:这个消费者关联的队列。值是@Queue
,代表一个队列
exchange
:队列所绑定的交换机,值是@Exchange
类型
key
:队列和交换机绑定的RoutingKey
死信队列和延迟队列
1、死信队列
死信队列:如果队列里的消息出现以下情况,那么该消息将成为“死信”。
- 消息被否定确认,使用 channel.basicNack 或 channel.basicReject ,并且此时requeue 属性被设置为false。
- 消息在队列的存活时间超过设置的TTL时间。
- 消息队列的消息数量已经超过最大队列长度。
“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列。那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
2、延迟队列
如果队列不设置TTL,表示消息永远不会过期
如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃
一般死信队列和延时队列一起使用,使用场景:
订单在十分钟之内未支付则自动取消。
新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
账单在一周内未支付,则自动结算。
用户注册成功后,如果三天内没有登陆则进行短信提醒。
用户发起退款,如果三天内没有得到处理则通知相关运营人员。
预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。
3、创建业务队列、死信队列、延迟队列
咱一块一块看。。提前声明:以下各部分代码均写在该配置类RabbitmqConfig 中
@Configuration
public class RabbitmqConfig {
}
第一块:业务队列、业务交换机部分
//业务队列业务交换机绑定:业务队列绑定死信队列(业务队列消息以后丢弃时会路由到死信队列) @Bean public Exchange test3Exchange(){ return ExchangeBuilder.topicExchange("test3.exchange") .ignoreDeclarationExceptions() .durable(true) .build(); } @Bean public Queue test3Queue(){ return QueueBuilder.durable("test3.queue")//持久化并指定队列名称 .deadLetterExchange("dead.exchange") .deadLetterRoutingKey("dead.msg") .build(); } @Bean public Binding test3Binding(Exchange test3Exchange,Queue test3Queue){ return BindingBuilder.bind(test3Queue) .to(test3Exchange) .with("test3.*") .noargs(); }
看点在于创建业务队列 test3Queue()时,
-----------------------------------
第二块:死信队列、死信交换机部分
//死信队列死信交换机 绑定 @Bean public Exchange deadExchange(){ return ExchangeBuilder.topicExchange("dead.exchange") .ignoreDeclarationExceptions() .durable(true) .build(); } @Bean public Queue deadQueue(){ return QueueBuilder.durable("dead.queue") // .expires() 队列未使用被删除的时间 //=====绑定延迟队列 .ttl(15000) //消息未被消费的存活时间 超过时长消息会被丢弃 .deadLetterExchange("delay.exchange") .deadLetterRoutingKey("delay.msg") .build(); } @Bean public Binding deadBinding(Exchange deadExchange,Queue deadQueue){ return BindingBuilder.bind(deadQueue) .to(deadExchange) .with("dead.msg") .noargs(); }
看点在于,延迟交换机就是死信交换机,也是使用deadLetterExchange
-------------------------------------
第三块:延迟队列、延迟交换机部分
//延迟队列延迟交换机 绑定 @Bean public Exchange delayExchange(){ return ExchangeBuilder.topicExchange("delay.exchange") .ignoreDeclarationExceptions() .durable(true) .build(); } @Bean public Queue delayQueue(){ return QueueBuilder.durable("delay.queue") .build(); } @Bean public Binding delayBinding(Exchange delayExchange,Queue delayQueue){ return BindingBuilder.bind(delayQueue) .to(delayExchange) .with("delay.msg") .noargs(); }
3、死信队列和延迟队列测试
1.在消息的消费者工程里: 故意出现异常2/0
@RabbitListener(queues = {
"test3.queue"
})
public void test3Listener(Message message , Map map , Channel channel) throws IOException {
try {
System.out.println(map);
int i = 2/0;
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
e.printStackTrace();
if(message.getMessageProperties().isRedelivered()){
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}else{
channel.basicNack(message.getMessageProperties().getDeliveryTag(),
false,true);
}
}
}
2.在生产者工程发送如下消息
Map map = new HashMap<>();
map.put("spuid","123456789");
map.put("operate","1");
//参数1:交换机名称 参数2:路由key 参数3:消息内容
rabbitTemplate.convertAndSend("test3.exchange", "test3.hehe" , map);
3.查看现象
观察消费者服务控制台,抛出两次异常(channel.basicNack重试)后消息会进入到死信队列,15秒后消息就到了延迟队列。
D:消息持久化
TTL:消息过期时间
DLX:死信交换机
DLK:死信路由