RabbitMQ高级特性_消费端限流   , [解耦, 限流,降低压力,发送消息]

rabbitmq连接池java rabbitmq连接数上限_spring

通过消费端限流的 方式限制消息的拉取速度,达到保护消费端的目的。

下面我们新建springboot项目进行测试:

新建项目myproducer

rabbitmq连接池java rabbitmq连接数上限_rabbitmq_02

依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

对application.yml进行配置:

# 配置RabbitMQ
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 开启确认模式
    publisher-confirm-type: correlated
    # 开启退回模式
    publisher-returns: true

先手动创建一个交换机(代码创建在下面)

rabbitmq连接池java rabbitmq连接数上限_java-rabbitmq_03

 我们先做个不做任何限流的操作进行查看

package com.pb.demo;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;、
@SpringBootTest
class MyproducerApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testSearchBatch() {
        for(int i = 1; i <=10; i++) {
            rabbitTemplate.convertAndSend("springboot_exchange", "my_routing", "send message....." + i);
        }
    }
}

然后创建交换机 springboot_exchange

创建队列my_queue 并绑定路由my_routing

rabbitmq连接池java rabbitmq连接数上限_分布式_04

 绑定

rabbitmq连接池java rabbitmq连接数上限_rabbitmq_05

rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_06

然后启动测试看看数据能不能发送到队列中去

rabbitmq连接池java rabbitmq连接数上限_分布式_07

 然后创建消费端项目myconsumer

rabbitmq连接池java rabbitmq连接数上限_分布式_08

 对application.yml配置文件进行配置

# 配置RabbitMQ
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        # 限流机制必须开启手动签收
        acknowledge-mode: manual

创建包myconsumer并新建类OosConsumer我们来消费消息

package com.pb.demo.myconsumer;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class OosConsumer {
    @RabbitListener(queues = "my_queue")
    public void linsenerConsumer(Message message, Channel channel) throws IOException, InterruptedException {
        System.out.println("收到了消息:" + new String(message.getBody()));
        //睡眠下
        Thread.sleep(3000L);
        //签收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

启动主启动类进行测试:

我们可以看rabbitmq的控制台

rabbitmq连接池java rabbitmq连接数上限_分布式_09

 启动Consumer的启动类  在启动producer的测试类  可以看到 上面的情况

我么发现消息已经被消费掉了

我们会发现他把所有的消息都堆到unacked中,那么就说明所有的消息都会堆到消费者中,因为我们这里并没有开启限流操作,如果我们有10万条消息那么就会造成消费者的内存溢出或者内存泄漏的问题


下面我们来开启限流注意如果我们开启限流必须是手动签收

修改application.yml在消费者的项目中:

# 配置RabbitMQ
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        # 限流机制必须开启手动签收
        acknowledge-mode: manual
        # 消费端最多拉取5条消息进行消费,签收后消费端不满5条才会继续拉取消息
        prefetch: 5


rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_10

RabbitMQ高级特性_利用限流实现不公平分发

在RabbitMQ中,多个消费者监听同一条队列,则队列默认采用的轮询分发。但是在某种场景下这种策略并不是很好,例如消费者1处 理任务的速度非常快,而其他消费者处理速度却很慢。此时如果采 用公平分发,则消费者1有很大一部分时间处于空闲状态。此时可以 采用不公平分发,即谁处理的快,谁处理的消息多。

首先我们模拟公平分发的操作,或者说看看平均分发的弊端

项目myproducer中的生产者的代码不需要动

我们只需要改变项目myconsumer的代码,在myconsumer包中创建类UnfairConsumer,在内部创建两个消费者

记得把OosConsumer类中的消费者注释掉

package com.pb.demo.myconsumer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class UnfairConsumer {
    // 消费者1
    @RabbitListener(queues = "my_queue")
    public void listenMessage1(Message message, Channel channel) throws IOException, InterruptedException {
        // 1.获取消息
        System.out.println("消费者1:"+new String(message.getBody()));
        // 2.模拟业务处理
        Thread.sleep(500); // 消费者1处理快
        // 3.签收消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
    }

    // 消费者2
    @RabbitListener(queues = "my_queue")
    public void listenMessage2(Message message, Channel channel) throws IOException, InterruptedException {
        // 1.获取消息
        System.out.println("消费者2:"+new String(message.getBody()));
        // 2.模拟业务处理
        Thread.sleep(3000); // 消费者2处理快
        // 3.签收消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
    }
}

我们要消费端的限流prefetch: 5 先注释掉

# 配置RabbitMQ
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        # 限流机制必须开启手动签收
        acknowledge-mode: manual
        # 消费端最多拉取5条消息进行消费,签收后消费端不满5条才会继续拉取消息
        #prefetch: 5

好启动所有项目进行对消费者控制台的查看

rabbitmq连接池java rabbitmq连接数上限_java-rabbitmq_11

我们来实现不公平分发,谁处理的快就让他多处理

在消费端项目中修改application.yml配置文件:

# 配置RabbitMQ
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        # 限流机制必须开启手动签收
        acknowledge-mode: manual
        # 消费端最多拉取5条消息进行消费,签收后消费端不满5条才会继续拉取消息
        #prefetch: 5
        # 消费端最多拉取1条消息进行消费,这样谁处理的快谁拉取下一条消息,实现了不公平分发
        prefetch: 1

 从新启动项目进行测试:

来查看消费者的控制台:

 

rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_12



RabbitMQ高级特性_消息存活时间

RabbitMQ可以设置消息的存活时间(Time To Live,简称TTL), 当消息到达存活时间后还没有被消费,会被移出队列。RabbitMQ 可以对队列的所有消息设置存活时间【需要在创建队列的时候操作】,也可以对某条消息设置存活时间【需要在发送的的时候进行设置】

设置队列所有消息存活时间

在这里我们需要创建新的交换机和队列:

在项目myproducer中创建配置类在包com.pb.demo下新建配置类RabbitConfig

package com.pb.demo;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    private final String EXCHANGE_NAME="my_topic_exchange2";
    private final String QUEUE_NAME="my_queue2";

    // 1.创建交换机
    @Bean("bootExchange2")
    public Exchange getExchange(){
        return ExchangeBuilder
                .topicExchange(EXCHANGE_NAME) // 交换机类型
                .durable(true) // 是否持久化
                .build();
    }
    @Bean("bootExchange2")
    public Exchange getExchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    // 2.创建队列
    @Bean("bootQueue2")
    public Queue getMessageQueue(){
        return QueueBuilder
                .durable(QUEUE_NAME) // 队列持久化
                .ttl(10000)    //对了的存活时间
                .build();
    }

    // 3.将队列绑定到交换机
    @Bean
    public Binding bindMessageQueue(@Qualifier("bootExchange2") Exchange exchange, @Qualifier("bootQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();
    }
}

然后在myproducer项目的测试类发送10条信息

@Test
public void testSearchBatch2() {
    for(int i = 1; i <=10; i++) {
        rabbitTemplate.convertAndSend("my_topic_exchange2", "my_routing", "send message....." + i);
    }
}

rabbitmq连接池java rabbitmq连接数上限_分布式_13

 没有消费 的情况下:

设置单条消息存活时间【主要是在发送消息的时候设置单条消息的存活时间即可】

在myproducer项目的测试类进行操作:

@Test
public void testSendMessage() {
    // 1.创建消息属性
    MessageProperties messageProperties = new MessageProperties();
    // 2.设置存活时间
    messageProperties.setExpiration("10000");
    // 3.创建消息对象
    Message message = new Message("send message...".getBytes(), messageProperties);
    // 4.发送消息
    rabbitTemplate.convertAndSend("springboot_exchange", "my_routing", message);
}

然后测试该方法看看能不能给发送的单个消息设置时间

rabbitmq连接池java rabbitmq连接数上限_spring_14

10秒再次查看

rabbitmq连接池java rabbitmq连接数上限_rabbitmq_15

我们发现消息已经被清除掉了

注意:

1 如果设置了单条消息的存活时间,也设置了队列的存活时 间,以时间短的为准。
2 消息过期后,并不会马上移除消息,只有消息消费到队列顶端时,才会移除该消息。

下面我们来测试下

生产者写

@Test
public void testSendMessage2() {
    for(int i = 0; i < 10; i++) {
        //当i==5的时候我们来设置单条信息的时间
        if(i == 5) {
            // 1.创建消息属性
            MessageProperties messageProperties = new MessageProperties();
            // 2.设置存活时间
            messageProperties.setExpiration("10000");
            // 3.创建消息对象
            Message message = new Message("send message...".getBytes(), messageProperties);
            // 4.发送消息
            rabbitTemplate.convertAndSend("springboot_exchange", "my_routing", message);
        }
        else {   //否则发送普通消息
            rabbitTemplate.convertAndSend("springboot_exchange" , "my_routing", "send message" + i);
        }
    }
}

rabbitmq连接池java rabbitmq连接数上限_spring_16

发现并没有被立即删除,但是我们一定要注意第5条消息现在已经不能被消费了,因为mq默认他已经被删除了如果在10秒以后,因为他已经过期了

我们测试下,启动任何一个消费端进行查看:

@Component
public class MyConsumer3 {
    @RabbitListener(queues="my_queue")
    public void getMsge(Message message, Channel channel) throws IOException, InterruptedException {
        System.out.println("my_queue1收到了消息:" + new String(message.getBody()));
        //签收
        //睡眠下    Thread.sleep(500L);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_17

//因为网络问题致使我在点击添加过程中有可能多次发送消息,那么数据库可能添加多个数据怎么保证唯一性
//幂等性,乐观锁

RabbitMQ高级特性_优先级队列

假设在电商系统中有一个订单催付的场景,即客户在一段时间内未付款会给用户推送一条短信提醒,但是系统中分为大型商家和小型 商家。比如像苹果,小米这样大商家一年能给我们创造很大的利润,所以在订单量大时,他们的订单必须得到优先处理,此时就需 要为不同的消息设置不同的优先级,此时我们要使用优先级队列。

优先级队列用法如下:

创建队列和交换机

在项目myproducer中创建配类我们来创建交换机和队列

package com.pb.demo;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig1 {
    private final String EXCHANGE_NAME="priority_exchange";
    private final String QUEUE_NAME="priority_queue";

    // 1.创建交换机
    @Bean(EXCHANGE_NAME)
    public Exchange getExchange(){
        return ExchangeBuilder
                .topicExchange(EXCHANGE_NAME) // 交换机类型
                .durable(true) // 是否持久化
                .build();
    }

    // 2.创建队列
    @Bean(QUEUE_NAME)
    public Queue getMessageQueue(){
        return QueueBuilder
                .durable(QUEUE_NAME) // 队列持久化
                .maxPriority(10) // 设置队列的最大优先级,最大可以设置255,但官网推荐不超过10,太高比较浪费资源
                .build();
    }

    // 3.将队列绑定到交换机
    @Bean
    public Binding bindMessageQueue(@Qualifier(EXCHANGE_NAME) Exchange exchange, @Qualifier(QUEUE_NAME) Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("my_routing").noargs();
    }
}

在项目myproducer中添加测试类中添加方法

//优先级操作
@Test
public void testSendMessage3() {
    for(int i = 0; i < 10; i++) {
        if(i == 5) {    //i == 5的时候优先级比较高
            // 1.创建消息属性
            MessageProperties messageProperties = new MessageProperties();
            // 2.设置优先级
            messageProperties.setPriority(9);
            // 3.创建消息对象
            Message message = new Message("send message...".getBytes(), messageProperties);
            // 4.发送消息
            rabbitTemplate.convertAndSend("priority_exchange", "my_routing", message);
        }
        else {   //否则发送普通消息
            rabbitTemplate.convertAndSend("priority_exchange" , "my_routing", "send message" + i);
        }
    }
}

启动生产者,先发送消息

我们通过浏览器查看已经有了10条消息了

rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_18

 

在项目myconsumer的包myconsumer下新建类PriorityConsumer进行消费

package com.pb.demo.myconsumer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class PriorityConsumer {
    @RabbitListener(queues = "priority_queue")
    public void listenMessage(Message message, Channel channel) throws IOException {
        // 获取消息
        System.out.println(new String(message.getBody()));
        // 手动签收
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }
}

然后我们再启动消费者我们进行测试:

rabbitmq连接池java rabbitmq连接数上限_java-rabbitmq_19

 我们发现第5条信息被提前消费了

这里最好把生产者的数据先放到队列中然后在启动消费者,否则效果不明显

RabbitMQ死信队列_概念

在MQ中,当消息成为死信(Dead message)后,消息中间件可以 将其从当前队列发送到另一个队列中,这个队列就是死信队列【死信队列就是回收没有用的文件,就是mq的回收站】。而 在RabbitMQ中,由于有交换机的概念,实际是将死信发送给了死 信交换机(Dead Letter Exchange,简称DLX)。死信交换机和死信队列和普通的没有区别。

rabbitmq连接池java rabbitmq连接数上限_spring_20

 

消息成为死信的情况:

1 队列消息长度到达限制。

2 消费者拒签消息,并且不把消息重新放入原队列。

3 消息到达存活时间未被消费。


RabbitMQ死信队列_代码实现

创建死信队列

首先清理mq的队列和交换机把所有的交换机和队列全部删除

在项目myproducer的com.pb.demo包下创建创建配置类RabbitConfig2

package com.pb.demo;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig2 {
    private final String DEAD_EXCHANGE = "dead_exchange";   //死信交换机
    private final String DEAD_QUEUE = "dead_queue";         //死信队列
    private final String NORMAL_EXCHANGE = "normal_exchange";    //普通交换机
    private final String NORMAL_QUEUE = "normal_queue";    //普通队列
    // 死信交换机
    @Bean(DEAD_EXCHANGE)
    public Exchange deadExchange(){
        return ExchangeBuilder
                .topicExchange(DEAD_EXCHANGE)
                .durable(true)
                .build();
    }
    // 死信队列
    @Bean(DEAD_QUEUE)
    public Queue deadQueue(){
        return QueueBuilder
                .durable(DEAD_QUEUE)
                .build();
    }
    /**
     * 死信交换机绑定死信队列
     * @param exchange
     * @param queue
     * @return
     */
    @Bean
    public Binding bindDeadQueue(@Qualifier(DEAD_EXCHANGE) Exchange exchange,@Qualifier(DEAD_QUEUE)Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("dead_routing")
                .noargs();
    }
    // 普通交换机
    @Bean(NORMAL_EXCHANGE)
    public Exchange normalExchange(){
        return ExchangeBuilder
                .topicExchange(NORMAL_EXCHANGE)
                .durable(true)
                .build();
    }
    // 普通队列
    @Bean(NORMAL_QUEUE)
    public Queue normalQueue(){
        return QueueBuilder
                .durable(NORMAL_QUEUE)
                .deadLetterExchange(DEAD_EXCHANGE) // 绑定死信
                .deadLetterRoutingKey("dead_routing") // 死信队列路由关键字
                .ttl(10000) // 消息存活10s
                .maxLength(10) // 队列最大长度 为10
                .build();
    }
    // 普通交换机绑定普通队列
    @Bean
    public Binding
    bindNormalQueue(@Qualifier(NORMAL_EXCHANGE)Exchange exchange,@Qualifier(NORMAL_QUEUE)Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("my_routing")
                .noargs();
    }
}

测试死信队列

生产者发送消息

在项目myproducer的测试类中创建方法并测试死信队列

/**
 * 测试死信队列
 * 测试3钟情况可能回把消息放入死信队列中
 */
@Test
public void testDealque() {
    // 存活时间过期后变成死信
    rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
    // 超过队列长度后变成死信
     
    //消息拒签但不返回原队列后变成死信
     
}

然后启动生产者进行测试

rabbitmq连接池java rabbitmq连接数上限_rabbitmq_21

 然后等待10秒钟:

rabbitmq连接池java rabbitmq连接数上限_rabbitmq_22

 

会发现直接把为消费的数据放入了死信队列中

然后修改测试类代码测试第二种放入死信队列的方式

/**
 * 测试死信队列
 * 测试3钟情况可能回把消息放入死信队列中
 */
@Test
public void testDealque() {
    // 存活时间过期后变成死信
    //rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
    // 超过队列长度后变成死信
    for (int i = 0; i < 20; i++) {
        rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
    }

    //消息拒签但不返回原队列后变成死信

}

 

rabbitmq连接池java rabbitmq连接数上限_分布式_23

 

我们发现我们发送了20条消息,有10条消息立刻就跑到了死信队列

然后过10秒钟我们再看

rabbitmq连接池java rabbitmq连接数上限_分布式_24

 

因为没有消费者,我们的另外10条消息也跑到了死信队列中

继续修改测试代码:

/**
 * 测试死信队列
 * 测试3钟情况可能回把消息放入死信队列中
 */
@Test
public void testDealque() {
    // 存活时间过期后变成死信
    //rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
    // 超过队列长度后变成死信
    /*for (int i = 0; i < 20; i++) {
        rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
    }*/
    //消息拒签但不返回原队列后变成死信
    rabbitTemplate.convertAndSend("normal_exchange","my_routing","测试死信");
}

这里需要创建一个消费者:

在项目myconsumer的包myconsumer下创建类DealConsumer

package com.pb.demo.myconsumer;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
 * 拒签操作
 */
@Component
public class DealConsumer {
    @RabbitListener(queues = "normal_queue")
    public void listenMessage(Message message, Channel channel) throws IOException {
        // 拒签消息
        channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,false);
    }
}

先来看下队列的状态:

rabbitmq连接池java rabbitmq连接数上限_spring_25

 然后先启动消费者,再启动生产者看看拒收后的状态

rabbitmq连接池java rabbitmq连接数上限_java-rabbitmq_26

 

我们看到因为消费者的拒收直接把消息放入了死信队列中

RabbitMQ延迟队列_概念 【延迟队列插件,linxu版本是有的,交换机的名称,队列名称】

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间 后,才会被消费。

例如:用户下单后,30分钟后查询订单状态,未支付则会取消订 单。

rabbitmq连接池java rabbitmq连接数上限_rabbitmq连接池java_27

 但RabbitMQ中并未提供延迟队列功能,我们可以使用死信队列实现延迟队列的效果。

rabbitmq连接池java rabbitmq连接数上限_分布式_28

 

RabbitMQ延迟队列_死信队列实现

接下来我们使用死信队列实现延迟队列

创建SpringBoot订单模块,添加SpringMVC、RabbitMQ、 lombok依赖。

新建项目myorder

rabbitmq连接池java rabbitmq连接数上限_分布式_29

 

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.pb.demo</groupId>
    <artifactId>myproducer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>myproducer</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Yml文件:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /

新建包config,并新建类RabbitConfig

package com.pb.demo.config;

import org.springframework.amqp.core.*;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    // 订单交换机和队列
    private final String ORDER_EXCHANGE = "order_exchange";
    private final String ORDER_QUEUE = "order_queue";
    // 过期订单交换机和队列【这里就是我们所说的死信队列】
    private final String EXPIRE_EXCHANGE = "expire_exchange";
    private final String EXPIRE_QUEUE = "expire_queue";

    // 过期订单交换机
    @Bean(EXPIRE_EXCHANGE)
    public Exchange deadExchange(){
        return ExchangeBuilder.topicExchange(EXPIRE_EXCHANGE).durable(true).build();
    }
    // 过期订单队列
    @Bean(EXPIRE_QUEUE)
    public Queue deadQueue(){
        return QueueBuilder.durable(EXPIRE_QUEUE).build();
    }

    // 将过期订单队列绑定到交换机
    @Bean
    public Binding bindDeadQueue(@Qualifier(EXPIRE_EXCHANGE) Exchange exchange,
                                 @Qualifier(EXPIRE_QUEUE) Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("expire_routing").noargs();
    }


    // 订单交换机
    @Bean(ORDER_EXCHANGE)
    public Exchange normalExchange(){
        return ExchangeBuilder.topicExchange(ORDER_EXCHANGE).durable(true).build();
    }

    // 订单队列
    @Bean(ORDER_QUEUE)
    public Queue normalQueue(){
        return QueueBuilder
                .durable(ORDER_QUEUE)
                .ttl(10000) // 存活时间为 10s,模拟30min
                .deadLetterExchange(EXPIRE_EXCHANGE) // 绑定死信交换机
                .deadLetterRoutingKey("expire_routing") //死信交换机的路由关键字
                .build();
    }

    // 将订单队列绑定到交换机
    @Bean
    public Binding
    bindNormalQueue(@Qualifier(ORDER_EXCHANGE) Exchange exchange,
                    @Qualifier(ORDER_QUEUE) Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("order_routing")
                .noargs();
    }
}

创建消费者:新建包com.pb.demo.consumer并新建类ExpireOrderConsumer

package com.pb.demo.consumer;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ExpireOrderConsumer {
    @RabbitListener(queues = "expire_queue")
    public void listenMessage(String orderId){
       /* try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        System.out.println("查询"+orderId+"号订单的状态,如果已支付则无需处理, 如果未支付则需要回退库存");
    }
}

创建controller:com.pb.demo.controller,并创建类OrderController

package com.pb.demo.controller;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/place/{orderId}")
    public String placeOrder(@PathVariable String orderId) {
        System.out.println("正在处理订单数据。。。。。");
        //讲订单id发送给订单队列
        rabbitTemplate.convertAndSend("order_exchange", "order_routing", orderId);
        return "下单成功,修改库存";
    }
}

浏览器测试就好