订单超时未支付自动关闭实现方案

一.场景

        生活中,12306购票、京东、淘宝购物下单,都会遇到请在30分钟内进行支付的场景,互联网电商项目的订单系统都需要解决订单超时问题。

        无独有偶,订单超时业务场景,非常符合“在一段时间之后,完成一个工作任务”的需求。今天,抽时间给大家总结了几种订单超时未支付自动关闭的实现方案。

二.实现方案

1、定时任务

数据库轮询方式,实现思路比较简单。启动一个定时任务,每隔一定时间扫描订单表,物理逻辑做处理,这种处理方式只是适合比较小而简单的项目。

假设订单表结构为:

t_order(订单Id、订单状态status、创建时间utc_time)

然后,定时任务每隔一个5min(自己设定时间)扫描数据库,通过下单时间和状态判断订单是否30分钟还未支付:

select orderId from t_order where 当前时间-utc_time > 30min and status = "未支付";

然后,关闭订单:

update t_order set status = "订单取消" where orderId in(超时订单id);

如果数据量很大,需要分页查询,分页update会是一个for循环。

优点:

        实现简单,无技术难点,异常恢复,支持分布式/集群环境。

缺点:

        影响数据库性能,时效性差,效率低。

2、 被动取消

这种实现方案和懒加载的思想一致,就是被动的取消订单。只有当用户查询订单消息时,再判断订单是否超时,如果超时再进行超时逻辑的处理。但是这种方式依赖于用户的查询操作触发,也就说如果用户不进行订单查询,该订单就永远不会被取消,这就会导致库存可能始终被占用。

所以,在实际项目中,很可能是被动取消 + 定时任务的组合实现方式解决超时订单。这种情况下定时任务的执行时间可以设置稍微“长”一点。

优点:

        实现思路简单。

缺点:

        (1)会产生额外影响,比如统计,订单,库存等;

        (2)影响用户体验,用户打开订单列表可能需要处理大量数据,影响显示的实时性。

3、延迟任务

JDK的延时队列

JDK自带了一个延时队列DelayQueue,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,必须实现Delayed接口。

// 定义一个Delay,放入到DelayQueue队列中,向队列中插入一个元素(延时任务)"以毫秒为单位的延迟"
taskService.addTask(new CancelOrderTask(outTradeNo, 1800000));
public class CancelOrderTask extends Task {

    private final Log logger = LogFactory.getLog(GenerateWalletTask.class);
    private String outTradeNo;

    public CancelOrderTask(String outTradeNo, long delayInMilliseconds) {
        super("CancelOrderTask-" + outTradeNo, delayInMilliseconds);
        this.outTradeNo = outTradeNo;
    }

    // 执行任务
    @Override
    public void run() {
        
        // 如果存在则取消订单,恢复对应库存
       
    }
}
public abstract class Task implements Delayed, Runnable{
    private String id = "";
    private long start = 0;

    public Task(String id, long delayInMilliseconds){
        this.id = id;
        this.start = System.currentTimeMillis() + delayInMilliseconds;
    }

    public String getId() {
        return id;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        long diff = this.start - System.currentTimeMillis();
        return unit.convert(diff, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return Ints.saturatedCast(this.start - ((Task) o).start);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;
        if (!(o instanceof Task)) {
            return false;
        }
        Task t = (Task)o;
        return this.id.equals(t.getId());
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
}
@Component
public class TaskService {
    private TaskService taskService;
    private DelayQueue<Task> delayQueue = new DelayQueue<Task>();

    @PostConstruct
    private void init() {
        taskService = this;

        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Task task = delayQueue.take();
                        task.run();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    public void addTask(Task task) {
        if (delayQueue.contains(task)) {
            return;
        }
        delayQueue.add(task);
    }

    public void removeTask(Task task) {
        delayQueue.remove(task);
    }

}

优点:

        实现简单,性能较好。

缺点:

        异常恢复困难,分布式/集群实现困难。

青年人的责任重大!努力吧...

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值