精华内容
下载资源
问答
  • 电商系统如何实现订单超时自动取消

    千次阅读 多人点赞 2021-05-04 16:57:51
    基于MQ的延迟队列和死信队列实现订单超时自动取消,分享详细流程、实现思路和代码内容。

    一、背景

    系统中用户下单,对于系统下单一般是分布式事务的操作,想要实现订单超时自动取消,我们可以基于MQ的延迟队列和死信队列实现。整体的实现思路分三种情况要考虑,第一种是订单的创建和投递到MQ,第二种是正常订单消息的消费,另外则是超时后消息的消费。

    二、实现思路

    对于订单的创建,只要生产者将消息成功投递到MQ,则认为订单创建成功。MQ返回ack表明消息投递成功,此时向延迟队列发送一条消息,而延迟队列挂载死信队列。这样做目的是:如果延迟队列中的消息达到阈值还没消费,则会进入死信队列,此时死信队列的监听器则会获取到过期的订单信息,可以做取消操作,反之,则走正常订单消费的流程。

    整体实现思路大体如下:

    在这里插入图片描述

    三、具体代码

    本文基于RabbitMQ实现,借助于RabbitMQ的延迟队列TTL和死信队列。
    配置文件:

    server.port=8080
    
    # 设计rabbitmq连接
    spring.rabbitmq.host=127.0.0.1
    spring.rabbitmq.port=5672
    spring.rabbitmq.username=root
    spring.rabbitmq.password=123456
    # 设置虚拟主机
    spring.rabbitmq.virtual-host=keduw-order
    
    # 设置发布者确认机制
    # correlated发布消息成功到交换器后会触发回调方法,默认是none
    spring.rabbitmq.publisher-confirm-type=correlated
    spring.rabbitmq.publisher-returns=true
    
    # 消息为手动确认
    spring.rabbitmq.listener.direct.acknowledge-mode=manual
    

    增加RabbitMQ的配置类,创建对应的队列、转换器、监听器以及队列信息绑定,备注很详细,这里就不太赘述。

    **
     * RabbitMQ配置类
     */
    @Configuration
    public class RabbitMqConfig {
    
        /**
         * 使用DirectMessageListenerContainer,您需要确保ConnectionFactory配置了一个任务执行器,
         * 该执行器在使用该ConnectionFactory的所有侦听器容器中具有足够的线程来支持所需的并发性。
         * 默认连接池大小仅为5。
         *
         * 并发性基于配置的队列和consumersPerQueue。每个队列的每个使用者使用一个单独的通道,
         * 并发性由rabbit客户端库控制;默认情况下,它使用5个线程池;
         * 可以配置taskExecutor来提供所需的最大并发性。
         *
         * @param connectionFactory
         * @return
         */
        @Bean(name = "rabbitMessageListenerContainer")
        public DirectMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory){
            // 写的时候,默认使用DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2个线程
            DirectMessageListenerContainer container = new DirectMessageListenerContainer(connectionFactory);
            // 设置确认消息的模式
            container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
            container.setPrefetchCount(5);
            container.setConsumersPerQueue(5);
            container.setMessagesPerAck(1);
    
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setCorePoolSize(10);
            taskExecutor.setMaxPoolSize(20);
            //设置该属性,灵活设置并发 ,多线程运行。
            container.setTaskExecutor(taskExecutor);
    
            return container;
        }
    
        /**
         * 设置消息转换器,用于将对象转换成JSON数据
         * 可以通过converterAndSend将对象发送消息队列
         * 监听器也可以通过该工具将接受对象反序列化成java对象
         *
         * @return Jackson转换器
         */
        @Bean
        public MessageConverter messageConverter(){
            return new Jackson2JsonMessageConverter();
        }
    
        @Bean
        public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
            return new RabbitAdmin(connectionFactory);
        }
    
        /**
         * 订单消息队列
         * @return
         */
        @Bean
        public Queue orderQueue(){
            return QueueBuilder.durable("q.order").build();
        }
    
        /**
         * 延迟消息队列
         * @return
         */
        @Bean
        public Queue ttlQueue(){
            Map<String,Object> args = new HashMap<>();
            // 该队列的消息10s到期
            args.put("x-message-ttl", 10000);
            // 设置死信队列交换器,(当队列消息TTL到期后依然没有消费,则加入死信队列)
            args.put("x-dead-letter-exchange","x.dlx");
            // 设置私信队列路由键,设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的routingKey
            args.put("x-dead-letter-routing-key","k.dlx");
            Queue queue = new Queue("q.ttl",true,false,false, args);
            return queue;
        }
    
        /**
         * 死信队列,用于取消用户订单
         * 当10s还没有付款的订单则进入死信队列,消费死信队列,取消用户订单
         *
         * @return
         */
        @Bean
        public Queue dlxQueue(){
            Map<String,Object> args = new HashMap<>();
            Queue dlq = new Queue("q.dlx",true,false,false, args);
    
            return dlq;
        }
    
        /**
         * 订单交换器
         * @return
         */
        @Bean
        public Exchange orderExchange(){
            Map<String, Object> args = new HashMap<>();
            DirectExchange exchange = new DirectExchange("x.order", true, false, args);
    
            return exchange;
        }
    
        /**
         * 延迟队列交换器
         * @return
         */
        @Bean
        public Exchange ttlExchange(){
            Map<String, Object> args = new HashMap<>();
            return new DirectExchange("x.ttl", true, false, args);
        }
    
        /**
         * 死信队列交换器
         * @return
         */
        @Bean
        public Exchange dlxExchange(){
            Map<String, Object> args = new HashMap<>();
            DirectExchange exchange = new DirectExchange("x.dlx", true, false, args);
            return exchange;
        }
    
        /**
         * 用于发送下单,做分布式事务的MQ
         * @return
         */
        @Bean
        public Binding orderBinding(){
            return BindingBuilder.bind(orderQueue())
                    .to(orderExchange())
                    .with("k.order")
                    .noargs();
        }
    
        /**
         * 用于等待用户支付的延迟队列绑定
         * @return
         */
        @Bean
        public Binding ttlBinding(){
            return BindingBuilder.bind(ttlQueue())
                    .to(ttlExchange())
                    .with("k.ttl")
                    .noargs();
        }
    
        /**
         * 用于支付超时取消用户订单的死信队列绑定
         * @return
         */
        @Bean
        public Binding dlxBinding(){
            return BindingBuilder.bind(dlxQueue())
                    .to(dlxExchange())
                    .with("k.dlx")
                    .noargs();
        }
    
    }
    

    创建订单监听器,用于监听订单正常的支付提交和超时取消。

    /**
     * 订单正常支付流程监听
     */
    @Component
    public class OrderNormalListener {
    
        @RabbitListener(queues = "q.order",ackMode = "MANUAL")
        public void onMessage(Order order , Channel channel , Message message) throws IOException {
            System.out.println("写入数据库");
            System.out.println(order);
    
            for (OrderDetail detail : order.getDetails()){
                System.out.println(detail);
            }
    
            channel.basicAck(message.getMessageProperties().getDeliveryTag() , false);
        }
    
    }
    

    创建订单超时自动取消监听器,监听的是死信队列。

    /**
     * 订单超时自动取消监听
     */
    @Component
    public class OrderCancelListener implements ChannelAwareMessageListener {
    
        @Override
        @RabbitListener(queues = "q.dlx" , ackMode = "MANUAL")
        public void onMessage(Message message, Channel channel) throws Exception {
            String orderId = new String(message.getBody());
            System.out.println("取消订单:" + orderId);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }
    

    对于订单提交,正常提交后同时投递多一份到延迟队列里面去,用作延迟取消。

    // 构建订单信息
    Order order = new Order();
    order.setUserId(IdUtils.generateUserId());
    order.setOrderId(IdUtils.generateOrderId());
    // 设置状态为待支付
    order.setStatus(OrderStatus.TO_BE_PAYED.toString());
    order.setDetails(details);
    
    // 投递消息
    CorrelationData correlationData = new CorrelationData();
    rabbitTemplate.convertAndSend("x.order","k.order", order, correlationData);
    // 同步等待,可以设置为异步回调
    CorrelationData.Confirm confirm = correlationData.getFuture().get();
    // 判断发送的消息是否得到broker的确认
    boolean confirmAck = confirm.isAck();
    if (confirmAck){
        // 发送延迟等待消息
        rabbitTemplate.convertAndSend("x.ttl","k.ttl" , order.getOrderId());
    }
    

    四、总结

    到这里,基本就实现了整个订单延迟自动取消的思路,但事实上还有问题。

    投递订单消息到MQ后要投递多一份到延迟队列,可能存在第一次投递成功但投递到延迟队列失败的情况,这里则需要依赖分布式锁或者增加补偿机制;还有编码上的问题,MQ队列名称这些最好抽离出来,当然这里只是demo,就没有那么规范,如果是产品开发,这些都需要最好规定,方便后期维护。

    展开全文
  • 大多数的B2C商城项目都会有限时活动,当用户下单后都会有支付超时时间,当订单超时后订单的状态就会自动变成已取消 ,这个功能的实现有很多种方法,本文的实现方法适合大多数比较小的商城使用。具体实现方式可以跟随...
  • 订单超时自动取消,延时任务

    千次阅读 2019-10-11 10:51:45
    应用场景,电商项目用户下单后超过指定时间未支付,订单自动失效。 https://blog.csdn.net/weixin_41690497/article/details/82996588

    应用场景,电商项目用户下单后超过指定时间未支付,订单自动失效。

    https://blog.csdn.net/weixin_41690497/article/details/82996588

    展开全文
  • 订单超时自动取消

    千次阅读 2015-01-29 17:09:44
    Java实现自动取消订单 这个功能我实际经验,民航某大航空公司的机票订单管理系统,订座45分钟付款,否者取消。 一: 1.quartz,每几分钟执行一次(根据订单处理速度,和订单生成情况)。 2.每次指定其中的...
    Java实现自动取消订单
    

    这个功能我实际经验,民航某大航空公司的机票订单管理系统,订座45分钟付款,否者取消。
    一:
    1.quartz,每几分钟执行一次(根据订单处理速度,和订单生成情况)。
    2.每次指定其中的更新条数,例如前1000条。
    3.这种方式的确会影响性能,所以要是系统订单比较多,推荐独立的定时服务器。

    二:
    采用2种方式混合来处理,节省资源,保证结果的完全准确性。
    1、采用主动触发的方式来取消订单。
    订单的表里面,再加入有效时间字段,如果查询的时候,如果订单为已下单未处理状态,查询有效字段,如果该字段的值少于当前时间,说明订单是有效的,可以对订单进行下一步的操作,如果该字段的值大于当前时间,直接更新订单状态为取消。

    2、每天凌晨定时处理(一条sql语句搞定),批量修改状态无效的订单。


    个人总结:
    方法一:
    1订单表里,加入有效时间字段
    2用户每次查询订单时,先对当日订单进行比较,更新,查询,显示结果
    3使用quartz每天晚上12点对数据库进行更新

    方法二:
    1订单表里,加入有效时间字段
    2增加独立服务器每分钟对数据库表前1000条进行更新

    原生quartz
    1添加QuartzManager.java
    2添加Jobstsk.java
    3ConfigServlet.java的load方法添加
    QuartzManager.addJob("shopcancel", "com.util.Jobstsk", "30 * * * * ?");//启动定时任务
    展开全文
  • delayQueue实现订单超时自动取消

    千次阅读 2020-06-15 10:09:20
    商城系统的订单模块都应该有:订单未支付超时自动取消订单的操作。我们在开发过程中实现该功能也有很多,例如 消息中间件、定时任务等,每种方法都有各自的优点。这里我使用java DelayQueue容器来实现,优点是本地...

    说明

    商城系统的订单模块都应该有:订单未支付超时后自动取消订单的操作。我们在开发过程中实现该功能也有很多,例如 消息中间件、定时任务等,每种方法都有各自的优点。这里我使用java DelayQueue容器来实现,优点是本地存储,系统资源消耗低,缺点是单机模式。

    实现

    1.编写Delayed实现类

    @Data
    @Accessors(chain = true)
    @NoArgsConstructor
    public class OrderAutoEntity implements Delayed {
    
        //订单编号
        private String orderNo;
        //订单具体的过期时间
        private long expire;
        //订单过期时间间隔定义(毫秒),这里设置30分钟
        public static final long expireTime = TimeUnit.MINUTES.toMillis(30);
    
        public OrderAutoEntity(String orderNo, LocalDateTime orderTime) {
            this.orderNo = orderNo;
            //转成毫秒
            long creatTime = orderTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
            this.expire = expireTime + creatTime;
        }
    
        /**
         * 获取剩余时间
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
    
        @Override
        public int compareTo(Delayed other) {
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS));
        }
    
    }
    

    2.编写DelayQueue业务类

    @PostConstruct 这个注解会让标注的方法在系统启动后自定加载

    @Service
    @Slf4j
    public class OrderCanelService {
    	
    	//订单增删改查业务逻辑
        @Resource
        private OrderService orderService;
        
        //用于存放需要未支付计时订单
        private final static DelayQueue<OrderAutoEntity> delayQueue = new DelayQueue<>();
    
    
        //订单取消,数据库改变订单状态,DelayQueue容器移除该订单记录
        public void cancelOrder(String orderNo) {
           //数据库改变订单状态
           orderService.cancelOrder(orderNo);
           //容器遍历找到对应的订单记录,并从容器中移除
           Iterator val = delayQueue.iterator();
           while (val.hasNext()) {
               OrderAutoEntity orderAutoEntity = (OrderAutoEntity) val.next();
               if(orderAutoEntity.getOrderNo().equals(orderNo)){
                   delayQueue.remove(orderAutoEntity);
               }
           }
        }
    
        //往队列中新增订单记录
        public void add(OrderAutoEntity orderAutoEntity) {
            delayQueue.put(orderAutoEntity);
        }
    
        /**
         * 服务器启动时,自动加载待支付订单
         */
        @PostConstruct
        public void initOrderStatus() {
            log.info(">>>>>>>>>>> 系统启动时,加载所有待支付订单到延时队列 >>>>>>>>>>>>.");
            //未支付订单查询
            QueryWrapper<Order> wrapper = new QueryWrapper();
            wrapper.select("order_no", "create_time").eq("status", "0");
            //获取所有未支付订单,这里用了mybatisplus操作数据库
            List<Map<String, Object>> orders = orderService.listMaps(wrapper);
            
            //逐个构造Delay的实现类,添加进容器
            for (Map<String, Object> order : orders) {
                Timestamp createTime = (Timestamp) order.get("create_time");
                OrderAutoEntity orderAutoEntity = new OrderAutoEntity((String) order.get("order_no"), createTime.toLocalDateTime());
                delayQueue.add(orderAutoEntity);
            }
            
            //启动一个线程持续健康订单超时
            Executors.newSingleThreadExecutor().execute(() -> {
                try {
                    while (true) {
                        if (delayQueue.size() > 0) {
                        	//容器中超时的订单记录会被取出
                            OrderAutoEntity order = delayQueue.take();
                            //修改数据库,容器中移除数据
                            cancelOrder(order.getOrderNo());
                        }
                    }
                } catch (InterruptedException e) {
                    log.error("InterruptedException error:", e);
                }
            });
        }
    }
    

    3.编写订单业务逻辑

    需要朱阿姨的是:我们前台每次新增订单时,也需要再给容器中添加一条记录。

    @RestController
    @Slf4j
    @RequestMapping("/api")
    public class OrderController {
    	@Resource
        private OrderCanelService orderCanelService ;
    	...
    	//前端页面调用的订单新增接口
    	@PostMapping("/order")
        @Transactional(rollbackFor = Exception.class)
        public ResponseEntity addOrder(@RequestBody Map<String, Object> params) {
    		...
    			//构造订单延时类(OrderAutoEntity),这里是伪代码
    			OrderAutoEntity orderAutoEntity = new OrderAutoEntity(orderNo, createTime);
                orderAutoService.add(orderAutoEntity);
    		...
    	}
    	...
    }
    

    总结说明

    我这种操作只支持单机情况,一般还可以进一步优化,利用redis:在新增订单时,除了存到数据库,再保留一份到redis中,这样我们在OrderCanelService 的初始化函数initOrderStatus中就不需要查数据库,每次启动就直接读redis中的数据。这里我就没做这么复杂。

    展开全文
  • 订单超时自动取消订单怎么解决

    千次阅读 2020-04-29 12:48:30
    参考:https://blog.csdn.net/verifocus/article/details/79135895
  • 主要介绍了用PHP+Redis实现延迟任务,实现自动取消订单功能,通过业务场景给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • Python实现订单超时自动取消

    千次阅读 2019-06-21 18:23:07
    用户下单之后,在规定时间内如果不完成付款,订单自动取消,并且释放库存使用技术:Redis键空间通知(过期回调)用户下单之后将订单id作为key,任意值作为值存入redis中,给这条数据设置过期时间,也就是订单超时的...
  • java订单超时自动关闭

    千次阅读 2020-03-17 12:08:56
    使用延时队列DelayQueue实现订单超时自动关闭 DelayQueue 是一个线程安全的队列。可以实现异步操作 首先创建一个订单实体类 @Getter @Setter public class OrderInfo implements Serializable , Delayed { private ...
  • RabbitMQ实现下单超时自动取消支付

    千次阅读 2020-08-07 20:41:35
    如果在这段时间内用户没有支付,则默认订单取消,该如何实现? 定期轮询(数据库等)用户下单成功,将订单信息放入数据库,同时将支付状态放入数据库,用户付款更改数据库状态。定期轮询数据库支付状态,如果超过30...
  • 订单超时自动关闭的实现方案总结

    千次阅读 2018-10-25 20:08:54
    统一来说,业务有“在一段时间之后,完成一个工作任务”的需求。 实现这种定时任务有哪些方法呢,来总结一下想到的方法。一、定时轮询 ...假设订单表的结构为:t_order(oid, finish_time, stars...
  • 统一来说,业务有“在一段时间之后,完成一个工作任务”的需求。 实现这种定时任务有...假设订单表的结构为:t_order(oid, finish_time, stars, status, …),更具体的,定时任务每隔一个小时会这么做一次: sele...
  • 实现一个小程序拼团 如果规定时间没拼到 就自动取消订单 并退款 在API 中实现 有没有什么好方法呢
  • import java.time.Duration; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class Order implements Delayed { ... } } 测试结果可以看出,当订单到达过期时间后订单就被取消
  • 关于订单超时问题网上查了下看大多数用rabbitmq去做,但不久前又了解到可以用ScheduledExecutorService线程池的方法做定时任务,也可以实现这种需求,求问哪种方法好一点
  • 需要用到redis的订阅功能 vi /etc/redis/redis.conf notify-keyspace-events “Ex”。 #x 代表了过期事件。...index.php 创建订单,发布消息,10s后查询订单状态并更新订单 <?php require_once 'Re...
  • 公司电商APP要实现订单自动取消,自动确认收货,以及在规定时间内多人拼单功能,这里我们采用了beanstalkd队列消息中间件,简单来讲就是用到了beanstald的管道与任务,这里自动取消,自动确认,超时未拼单,自动转为...
  • 简单定时任务解决方案:使用redis的keyspace notifications(键...1、当一个业务触发以后需要启动一个定时任务,在指定时间内再去执行一个任务(如自动取消订单,自动完成订单等功能) 2、redis的keyspace notifica...
  • 订单超时取消的实现,首先想到的是定时任务,但是这种实现方式在订单量较大的情况下是有问题的,而且时间也会有误差,最大时间差就是定时任务的执行间隔时间。 使用redis的过期监听事件可以比较好的解决这个问题。...
  • 2: 创建取消时间字段,这个时间就是队列中你这条数据何时取消的时间 private Date cancelTime; 3:覆盖其compareTo方法 4:在操作的时候定义好取消时间,保存; 例如某条数据,我需要在一小时后执行某操作,需要在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,337
精华内容 2,534
关键字:

订单超时自动取消