热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot+RabbitMQ实现订单超时自动取消功能

场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消。如何订单超时实现?定时任务redission延时任务ra

场景:在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内
用户没有支付,则默认订单取消。

如何订单超时实现?
  • 定时任务
  • redission延时任务
  • rabbitmq死信队列

本文将以rabbitmq死信队列展开做讲解,因为定时任务的方式,是有点问题的,原本业务系统希望10分钟后,如果订单未支付,就马上取消订单,并释放商品库存。但是一旦数据量大的话,就会加长获取未支付订单数据的时间,部分订单就做不到10分钟后取消了,可能是15分钟,20分钟之类的。这样的话,库存就无法及时得到释放,也就会影响成单数。而使用rabbitmq死信队列,在定义业务队列时可以考虑指定一个死信交换机,并绑定一个死信队列。当消息变成死信时,该消息就会被发送到该死信队列上,再取出订单信息进行判断订单是否已支付,如未支付则讲订单状态修改为取消状态,这样也是可以达到订单超时取消的需求的。

软件准备

erlang

请参考Win10下安装erlang

https://blog.csdn.net/linsongbin1/article/details/80170487

RabbitMQ

https://blog.csdn.net/linsongbin1/article/details/80170567

启动RabbitMQ,然后添加一个用户, 并给用户设置权限。

# 后台启动
rabbitmq-server -detached
# 添加用户
rabbitmqctl add_user root 123456
# 设置用户权限:
rabbitmqctl set_permissions -p "/" root ".*" ".*" ".*"

接下来创建一个springboot工程,并集成RabbitMQ。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.8.RELEASE
         
    
    com.lagou
    rabbitmq-work
    0.0.1-SNAPSHOT
    rabbitmq-work
    Demo project for Spring Boot
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            mysql
            mysql-connector-java
            5.1.47
            runtime
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.2
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


接下来在application.yml配置文件中假如rabbitmq配置

spring:
  application:
    name: rabbit-work
  # rabbitmq配置
  rabbitmq:
    host: localhost
    virtual-host: /
    username: root
    password: 123456
  # redis配置
  redis:
    timeout: 6000
    host: localhost
    port: 6379
    database: 0
  # 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=utf8&useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    hikari:
      minimum-idle: 3
      maximum-pool-size: 5
      max-lifetime: 30000
      connection-init-sql: SELECT 1
# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath:com.lagou.*.mapper/*.xml
  type-aliases-package: com.lagou.*.domain

定义RabbitConfig

@Configuration
public class RabbitConfig {

    /**
     * 订单队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue orderQueue() {
        Map argments = new HashMap<>();
        argments.put("x-message-ttl", 60000);
        argments.put("x-dead-letter-exchange", RabbitConstants.ORDER_DLX_EXCHANGE);
        argments.put("x-dead-letter-routing-key", RabbitConstants.ORDER_DLX_ROUTING_KEY);
        Queue queue = new Queue(RabbitConstants.ORDER_QUEUE, true, false, false, argments);
        return queue;
    }

    /**
     * 订单交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange orderExchange() {
        return new DirectExchange(RabbitConstants.ORDER_EXCHANGE, true, false, null);
    }

    /**
     * 订单路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding orderRouting() {
        return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(RabbitConstants.ORDER_ROUTING_KEY).noargs();
    }

    /**
     * 订单死信队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue orderDlxQueue() {
        Queue queue = new Queue(RabbitConstants.ORDER_DLX_QUEUE, true, false, false);
        return queue;
    }

    /**
     * 订单死信交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange orderDlxExchange() {
        return new DirectExchange(RabbitConstants.ORDER_DLX_EXCHANGE, true, false, null);
    }

    /**
     * 订单死信路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding orderDlxRouting() {
        return BindingBuilder.bind(orderDlxQueue()).to(orderDlxExchange()).with(RabbitConstants.ORDER_DLX_ROUTING_KEY).noargs();
    }

    /**
     * 库存队列
     *
     * @return {@link Queue}
     */
    @Bean
    public Queue stockQueue() {
        return new Queue(RabbitConstants.STOCK_QUEUE, true, false, false, null);
    }

    /**
     * 库存交换机
     *
     * @return {@link Exchange}
     */
    @Bean
    public Exchange stockExchange() {
        return new DirectExchange(RabbitConstants.STOCK_EXCHANGE, true, false, null);
    }

    /**
     * 库存路由键
     *
     * @return {@link Binding}
     */
    @Bean
    public Binding stockRouting() {
        return BindingBuilder.bind(stockQueue()).to(stockExchange()).with(RabbitConstants.STOCK_ROUTING_KEY).noargs();
    }

}

实现消息发送

@RequestMapping(value = "/submit", produces = "application/json;charset=UTF-8")
public R submit(@RequestBody OrderVo orderVo) throws UnsupportedEncodingException {
	Order order = orderService.createOrder(orderVo);
	System.out.println("OrderController.submit... createOrder");

	// 放入死信队列
	amqpTemplate.convertAndSend(RabbitConstants.ORDER_EXCHANGE,
			RabbitConstants.ORDER_ROUTING_KEY,
			(order.getId() + ""));
	System.out.println("OrderController.submit... sendMessage to orderExchange");
	return R.ok(order);
}

消息消费者监听

@Component
public class OrderHandler {

    @Autowired
    private OrderService orderService;

    @RabbitListener(queues = RabbitConstants.ORDER_DLX_QUEUE, ackMode = "MANUAL")
    public void onMessage(Message message, Channel channel) throws IOException {
        System.out.println("消息进入死信队列...");
        String s = new String(message.getBody());
        Order o = orderService.getById(Long.parseLong(s));
        if (o != null && OrderConstants.TOPAID.getCode().equals(o.getOrderState())) {
            Order order = new Order();
            order.setId(o.getId());
            order.setOrderState(OrderConstants.CANCEL.getCode());
            order.setGmtModified(new Date());
            orderService.updateById(order);
        }
        // 手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

启动Spring Boot应用进行测试

Spring Boot + RabbitMQ实现订单超时自动取消功能

点击其中一个有库存的商品进行购买,先演示一个成功支付的。

Spring Boot + RabbitMQ实现订单超时自动取消功能

接下来再演示支付失败的,并注意控制台日志打印。

Spring Boot + RabbitMQ实现订单超时自动取消功能

Spring Boot + RabbitMQ实现订单超时自动取消功能

通过监听死信队列,消息在进入死信队列之后就可以做一系列业务逻辑处理,比如,消息如果还是未支付状态,将其修改为取消支付。


推荐阅读
  • 在RabbitMQ中,消息发布者默认情况下不会接收到关于消息在Broker中状态的反馈,这可能导致消息丢失的问题。为了确保消息的可靠传输与投递,可以采用确认机制(如发布确认和事务模式)来验证消息是否成功抵达Broker,并采取相应的重试策略以提高系统的可靠性。此外,还可以配置消息持久化和镜像队列等高级功能,进一步增强消息的可靠性和高可用性。 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • 在 CentOS 7 上部署和配置 RabbitMQ 消息队列系统时,首先需要安装 Erlang,因为 RabbitMQ 是基于 Erlang 语言开发的。具体步骤包括:安装必要的依赖项,下载 Erlang 源码包(可能需要一些时间,请耐心等待),解压源码包,解决可能出现的错误,验证安装是否成功,并将 Erlang 添加到环境变量中。接下来,下载 RabbitMQ 的 tar.xz 压缩包,并进行解压和安装。确保每一步都按顺序执行,以保证系统的稳定性和可靠性。 ... [详细]
  • Spring框架中的面向切面编程(AOP)技术详解
    面向切面编程(AOP)是Spring框架中的关键技术之一,它通过将横切关注点从业务逻辑中分离出来,实现了代码的模块化和重用。AOP的核心思想是将程序运行过程中需要多次处理的功能(如日志记录、事务管理等)封装成独立的模块,即切面,并在特定的连接点(如方法调用)动态地应用这些切面。这种方式不仅提高了代码的可维护性和可读性,还简化了业务逻辑的实现。Spring AOP利用代理机制,在不修改原有代码的基础上,实现了对目标对象的增强。 ... [详细]
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • Spring框架入门指南:专为新手打造的详细学习笔记
    Spring框架是Java Web开发中广泛应用的轻量级应用框架,以其卓越的功能和出色的性能赢得了广大开发者的青睐。本文为初学者提供了详尽的学习指南,涵盖基础概念、核心组件及实际应用案例,帮助新手快速掌握Spring框架的核心技术与实践技巧。 ... [详细]
  • 一文了解消息中间件RabbitMQ
    消息中间件---RabbitMQ1消息中间件的作用2.常用的消息中间件3消息中间件RabbitMQ3.1RabbitMQ介绍3.3RabbitMQ的队列模式3.3RabbitMQ的 ... [详细]
  • 可参照github代码:https:github.comrabbitmqrabbitmq-tutorialsblobmasterjavaEmitLogTopic.ja ... [详细]
  • RocketMQ在秒杀时的应用
    目录一、RocketMQ是什么二、broker和nameserver2.1Broker2.2NameServer三、MQ在秒杀场景下的应用3.1利用MQ进行异步操作3. ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 双指针法在链表问题中应用广泛,能够高效解决多种经典问题,如合并两个有序链表、合并多个有序链表、查找倒数第k个节点等。本文将详细介绍这些应用场景及其解决方案。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 虚拟网络连接配置指南旨在详细阐述如何在两台区域边界路由器(ABR)之间,通过一个非骨干区域(即传输区域)建立一条逻辑连接通道。该指南提供了具体的配置步骤和最佳实践,帮助网络管理员高效地实现跨区域的虚拟连接,确保网络的稳定性和可靠性。 ... [详细]
  • 考前准备方面,我的考试时间安排在上午11点至12点,只需提前20分钟到达考场的接待休息区即可。由于我居住在福田区,交通便利,可以选择多种方式前往考场。为了确保顺利通过考试,我建议考生提前熟悉考试流程和环境,并合理规划出行时间,以保持良好的心态和状态。此外,考前复习应注重理论与实践相结合,多做模拟题,加强对重点知识点的理解和掌握。 ... [详细]
  • 作为140字符的开创者,Twitter看似简单却异常复杂。其简洁之处在于仅用140个字符就能实现信息的高效传播,甚至在多次全球性事件中超越传统媒体的速度。然而,为了支持2亿用户的高效使用,其背后的技术架构和系统设计则极为复杂,涉及高并发处理、数据存储和实时传输等多个技术挑战。 ... [详细]
author-avatar
多米音乐_35692689
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有