RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单

测试前言

RabbitMQ 作为目前应用相当广泛的消息中间件,在企业级应用、微服务应用中充当着重要的角色。特别是在一些典型的应用场景以及业务模块中具有重要的作用,比如业务服务模块解耦、异步通信、高并发限流、超时业务、数据延迟处理等。
这篇文章带领大家使用RabbitMQ实现延时队列

1.搭建项目环境

工欲善其事,必先利其器,接触一个新技术之前,肯定要先安装环境和工具,本篇文章不提供安装教程,下方提供了一个安装RabbitMQ的博客:
RabbitMQ安装教程
安装成功之后RabbitMQ的运行界面就是这样的
RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第1张图片
这样我们的项目环境就搭建成功了。

2.延时队列–实现思路

延迟队列,也叫“延时队列”,顾名思义,其实就是“生产者生产消息,消息进入队列之后,并不会立即被指定的消费者所消费,而是会延时一段指定的时间TTL(Time To Live),最终才被消费者消费

RabbitMQ本身是不支持延时队列,而是同过二个特性来实现的:

  1. Time To Live(TTL)
    RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)
    RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
    A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
    B: 对消息进行单独设置,每条消息TTL可以不同。
    如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter(死信)

  2. Dead Letter Exchanges(DLX)
    RabbitMQQueue可以配置x-dead-letter-exchangex-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter(死信),则按照这两个参数重新路由转发到指定的队列。
    x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
    x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送路由

可能说这么多还是看不太懂,我准备了一张图
RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第2张图片
延时队列的执行流程就是图中所示

2.1 延时队列–应用场景

介绍完延时队列的概念之后,给大家举一个在项目中常见的场景:

用户创建下单记录之后,会对其进行付款,付款成功之后,该条记录将变为已支付并且有效,否则的话,一旦过了指定的时间,即超时了,则该记录将置为无效,并且不能被用于后续的业务逻辑

可能有人用过定时器(Timer)也可以实现类似的功能,但是定时器不能精准的知道哪些需要执行任务,查询范围太大,太浪费性能。
使用
rabbitmap
,我们只用把需要把的某个订单放入消息中间去(message),并且设置该消息的过期时间,等过期时间到达时再取出消费即可。
下面我们就用延时队列来实现,某个时间段过后取消未付款的订单

3.使用springboot+RabbitMq进行测试

  1. 使用Idea搭建springboot项目
    RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第3张图片2.在pom.xml中引入项目需要的jar包
  <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>
        </dependency>

3.配置文件application.properties

#本机ip地址,一般装在本机直接使用localhost,若是虚拟机,则使用虚拟机的ip地址
spring.rabbitmq.host=localhost 
# 端口号
spring.rabbitmq.port=5672
# rabbitmq的用户信息,默认都为guest
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

4.在与springboot启动类同级新建config和pojo和controller包
RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第4张图片
首先新建实体类:Order

/**
* 作者:LSH
*/
@Data
public class Order implements Serializable {
    private static final long serialVersionUID = -2221214252163879885L;

    private String orderId; // 订单id

    private Integer orderStatus; // 订单状态 0:未支付,1:已支付,2:订单已取消

    private String orderName; // 订单名字
}

5.配置队列
在config下面新建DelayRabbitConfig.java,将它作为一个配置类使用(copy之前记得看注释

@Configuration
@Slf4j
public class DelayRabbitConfig {
}

5.1 下面我们新建常量来命名立即消费队列和延时消费队列

     // 延迟队列 TTL 名称
    private static final String ORDER_DELAY_QUEUE = "order.delay.queue";
  
     // DLX,dead letter发送到的 exchange
     // 延时消息就是发送到该交换机的
    public static final String ORDER_DELAY_EXCHANGE = "order.delay.exchange";
    
     // routing key 名称
     // 具体消息发送在该 routingKey 的
    public static final String ORDER_DELAY_ROUTING_KEY = "order_delay";
    
    //立即消费的队列名称
    public static final String ORDER_QUEUE_NAME = "order.queue";
    
    // 立即消费的exchange 
    public static final String ORDER_EXCHANGE_NAME = "order.exchange";
    
    //立即消费 routing key 名称
    public static final String ORDER_ROUTING_KEY = "order";

5.2 创建立即消费队列和延时队列

   //创建一个延时队列
    @Bean
    public Queue delayOrderQueue() { 
        Map<String, Object> params = new HashMap<>();
        // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
        params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME);
        // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
        params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY);
        return new Queue(ORDER_DELAY_QUEUE, true, false, false, params);
    }
    // 创建一个立即消费队列
    @Bean
    public Queue orderQueue() {
        // 第一个参数为queue的名字,第二个参数为是否支持持久化
        return new Queue(ORDER_QUEUE_NAME, true);
    }

5.3 新建立即消费队列的交换机和延时队列的交换机

     //延迟交换机
    @Bean
    public DirectExchange orderDelayExchange() { 
        // 一共有三种构造方法,可以只传exchange的名字, 第二种,可以传exchange名字,是否支持持久化,是否可以自动删除,
        // 第三种在第二种参数上可以增加Map,Map中可以存放自定义exchange中的参数
       // new DirectExchange(ORDER_DELAY_EXCHANGE,true,false);
        return new DirectExchange(ORDER_DELAY_EXCHANGE);
    }
    
    //立即消费交换机
    @Bean
    public TopicExchange orderTopicExchange() {
        return new TopicExchange(ORDER_EXCHANGE_NAME);
    }

5.4 把新建的队列和交换机进行绑定

   // 把延时队列和 订单延迟交换的exchange进行绑定 
    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY);
    }
   
    // 把立即队列和 立即交换的exchange进行绑定
    @Bean
    public Binding orderBinding() {
        // TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
        return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY);
    }
  1. 在config包下面新建生产者:DelaySender.java, 声明它是一个工具类,这里我们日志为了简化流程使用了springboot自带的日志
/**
 * 作者:LSH
 * 日期:2019/12/18 21:44
 * 生产者 生产消息
 */
@Component
@Slf4j
public class DelaySender {
    // AMQP 高级消息队列协议
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendDelay(Order order) {
        log.info("【订单生成时间】" + new Date().toString() +"【1分钟后检查订单是否已经支付】" + order.toString() );
        this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> {
            // 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
            message.getMessageProperties().setExpiration(1 * 1000 * 60 + "");
            return message;
        });
    }
}

这里声明的amqpTemplate接口,这个接口包含了发送和接收消息的一般操作,换种说法,它不是某个实现所专有的,所以AMQP存在于名称里。这个接口的实现与AMQP协议的实现紧密关联。

this.amqpTemplate.convertAndSend的第一个参数为延迟交换机的名称,第二个为延时消费routing-key,第三个参数为order操作对象,第四个参数为消息

7.在config包下面新建消费者:DelayReceiver.java

// 接收者 --消费者
@Component
@Slf4j
public class DelayReceiver {
    @RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME})
    public void orderDelayQueue(Order order, Message message, Channel channel) {
        log.info("###########################################");
        log.info("【orderDelayQueue 监听的消息】 - 【消费时间】 - [{}]- 【订单内容】 - [{}]", new Date(), order.toString());
        if (order.getOrderStatus() == 0) {
            order.setOrderStatus(2);
            log.info("【该订单未支付,取消订单】" + order.toString());
        } else if (order.getOrderStatus() == 1) {
            log.info("【该订单已完成支付】");
        } else if (order.getOrderStatus() == 2) {
            log.info("【该订单已取消】");
        }
    }
}

在这个类中我们定义了一个普通方法,可能你会很纳闷为什么这个普通方法为什么可以进行接收消息,主要还是这个注解: @RabbitListener

下面给大家简单了解下这个注解的作用

  • @RabbitListener注解的方法所在的类首先是一个bean,因此,实现 BeanPostProcessor接口对每一个初始化完成的bean进行处理。
    比如上面 DelayRabbitConfig.ORDER_QUEUE_NAME所在的方法就是一个BeanRabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第5张图片
  • 遍历bean中由用户自己定义的所有的方法,找出其中添加了@RabbitListener注解的方法
  • 读取上面找出的所有方法上@RabbitListener注解中的值,并为每一个方法创建一个RabbitListenerEndpoint,保存在RabbitListenerEndpointRegistrar类中
  • 在所有的bean都初始化完成,即所有@RabbitListener注解的方法都创建了endpoint之后,由我们配置的RabbitListenerContainerFactory将每个endpoint创建MessageListenerContainer
  • 最后创建上面的MessageListenerContainer
  • 至此,全部完成,MessageListenerContainer启动后将能够接受到消息,再将消息交给它的MessageListener处理消息

如果还想对这个注解和消息的实现过程可以看看这篇文章
RabbitListener的实现过程

  1. 最后在controller包下面新建TestController.java 进行测试
@RestController
public class TestController {
    @Autowired
    private DelaySender delaySender;

    @GetMapping("/sendDelay")
    public Object sendDelay() {
        Order order1 = new Order();
        order1.setOrderStatus(0);
        order1.setOrderId("123321123");
        order1.setOrderName("波音747飞机");

        Order order2 = new Order();
        order2.setOrderStatus(1);
        order2.setOrderId("2345123123");
        order2.setOrderName("豪华游艇");

        Order order3 = new Order();
        order3.setOrderStatus(2);
        order3.setOrderId("983676");
        order3.setOrderName("小米alpan阿尔法");

        delaySender.sendDelay(order1);
        delaySender.sendDelay(order2);
        delaySender.sendDelay(order3);
        return "test--ok";
    }
}

最后启动服务,打开浏览器,输入: http://localhost:8080/sendDelay
RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第6张图片
在这里插入图片描述
我们再进入rabbitmq里面看看我们的队列和交换机
交换机 在这里插入图片描述
队列
在这里插入图片描述
一分钟后观察控制台
RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单_第7张图片
可以看到未支付的订单已经改变状态,至此我们实现了一个简单的超时订单取消支付,后面可以根据自己的项目需求不断添加改变

总结

本文可能在许多rabbitmq的许多概念没有说的特别清楚,但是都是自己看了这么多文章自己的理解,如有问题欢迎指出!!

你可能感兴趣的:(RabbitMq基础入门,使用IDEA+Springboot+RabbitMq实现延时队列--取消超时订单)