当前位置: 代码网 > it编程>编程语言>Java > SpringBoot订单超时自动取消的三种主流实现方案

SpringBoot订单超时自动取消的三种主流实现方案

2025年07月23日 Java 我要评论
引言在电商、外卖、票务等业务中,“下单后若 30 分钟未支付则自动取消”是一道经典需求。实现方式既要保证 实时性,又要在 高并发 下保持 低成本、高可靠。本文基于 spring

引言

在电商、外卖、票务等业务中,“下单后若 30 分钟未支付则自动取消”是一道经典需求。实现方式既要保证 实时性,又要在 高并发 下保持 低成本、高可靠

本文基于 spring boot,给出 3 种生产级落地方案,并附完整代码与选型对比,方便快速决策。

一、需求拆解

功能点约束
触发条件创建时间 + 30 min 仍未支付
实时性秒级(理想) / 分钟级(可接受)
幂等重复取消需幂等
高并发峰值 10 w+/日
数据一致性不能漏单、不能错单

二、方案总览

方案核心机制实时性额外组件代码复杂度
① 定时任务@scheduled + db 扫描分钟级★☆☆
② 延迟队列rabbitmq ttl + dlx秒级rabbitmq★★☆
③ redis 过期事件key ttl + keyspace notify秒级redis★★☆

三、方案 1:定时任务(@scheduled)

1. 思路

周期性扫描订单表,把“创建时间 + 30 min < 当前时间”且状态为 pending 的订单置为 cancelled

2. 代码实现

@enablescheduling
@component
@requiredargsconstructor
public class ordercancelschedule {

    private final orderservice orderservice;

    /** 每 30s 跑一次,可根据数据量调整 */
    @scheduled(fixeddelay = 30_000)
    public void cancelunpaidorders() {
        localdatetime expirepoint = localdatetime.now().minusminutes(30);
        list<long> ids = orderservice.findunpaidbefore(expirepoint);
        if (!ids.isempty()) {
            int affected = orderservice.batchcancel(ids);
            log.info("自动取消订单 {} 条", affected);
        }
    }
}

3. 优化技巧

  • 分页 + 索引
create index idx_order_status_created on t_order(status, created_time);
  • 分片扫描:按 id 或时间分片,避免大表锁。
  • 单机多线程@async("cancelexecutor") + 线程池。

4. 优缺点

  • ✅ 零依赖、实现快
  • ❌ 数据量大时 db 压力大;实时性受轮询间隔限制

5. 适用场景

日订单 < 1 w,或作为兜底方案。

四、方案 2:rabbitmq 延迟队列

1. 思路

订单创建后发送一条 30 min ttl 的消息;到期自动路由到消费队列,消费者检查订单状态并取消。

2. 架构图

producer ──> delay exchange (x-delayed-message) ──> 30min ttl ──> cancel queue ──> consumer

3. 代码实现

3.1 声明交换机 & 队列

@configuration
public class rabbitdelayconfig {

    @bean
    public customexchange delayexchange() {
        map<string, object> args = map.of("x-delayed-type", "direct");
        return new customexchange("order.delay", "x-delayed-message", true, false, args);
    }

    @bean
    public queue cancelqueue() {
        return queuebuilder.durable("order.cancel.queue").build();
    }

    @bean
    public binding binding() {
        return bindingbuilder.bind(cancelqueue()).to(delayexchange()).with("order.cancel").noargs();
    }
}

3.2 发送延迟消息

@service
@requiredargsconstructor
public class orderpublisher {
    private final rabbittemplate rabbittemplate;

    public void createorder(order order) {
        // 1. 落库
        ordermapper.insert(order);
        // 2. 发送延迟消息
        rabbittemplate.convertandsend(
            "order.delay",
            "order.cancel",
            order.getid(),
            msg -> {
                msg.getmessageproperties().setdelay(30 * 60 * 1000); // 30 min
                return msg;
            }
        );
    }
}

3.3 消费并取消

@component
@rabbitlistener(queues = "order.cancel.queue")
public class cancelconsumer {
    private final orderservice orderservice;

    @rabbithandler
    public void handle(long orderid) {
        order order = orderservice.find(orderid);
        if (order != null && order.getstatus() == orderstatus.pending) {
            orderservice.cancel(orderid);
        }
    }
}

4. 优缺点

  • ✅ 实时性好(秒级);支持分布式;消息持久化
  • ❌ 需要 rabbitmq;链路更长

5. 适用场景

中高并发,需秒级取消,已用 mq 或愿意引入 mq。

五、方案 3:redis keyspace 过期事件

1. 思路

order:{id} 作为 key,30 min ttl;redis 键过期时推送事件;应用监听后取消订单。

2. redis 配置

# redis.conf
notify-keyspace-events ex

或 cli:

config set notify-keyspace-events ex

3. 代码实现

3.1 订单创建时写 redis

@service
public class orderservice {
    private final stringredistemplate redistemplate;

    public void createorder(order order) {
        ordermapper.insert(order);
        // value 随意,这里用 id
        redistemplate.opsforvalue()
          .set("order:" + order.getid(),
               string.valueof(order.getid()),
               duration.ofminutes(30));
    }
}

3.2 监听过期事件

@configuration
public class redislistenerconfig {

    @bean
    public redismessagelistenercontainer container(redisconnectionfactory cf) {
        redismessagelistenercontainer container = new redismessagelistenercontainer();
        container.setconnectionfactory(cf);
        container.addmessagelistener(
            (message, pattern) -> {
                string key = message.tostring();
                if (key.startswith("order:")) {
                    string orderid = key.substring(6);
                    // 幂等取消
                    orderservice.cancelifunpaid(long.valueof(orderid));
                }
            },
            new patterntopic("__keyevent@*__:expired")
        );
        return container;
    }
}

4. 幂等 & 可靠性

  • 幂等:取消 sql 加状态条件 where status = pending
  • 可靠性:redis 重启会丢失未过期 key,需 兜底定时任务(方案 1)双保险。

5. 优缺点

  • ✅ 实时性高,组件少
  • ❌ redis 重启可能丢事件;需处理幂等

6. 适用场景

已用 redis,订单量中等,能接受极低概率漏单。

六、3 种方案对比与选型

维度定时任务rabbitmq 延迟队列redis 过期事件
实时性分钟级秒级秒级
吞吐量
额外组件rabbitmqredis
可靠性中(需兜底)
实现复杂度★☆☆★★☆★★☆
推荐场景小流量、兜底高并发、已用 mq已用 redis、中等并发

建议

  1. 小项目 → 定时任务即可;
  2. 大流量 → 延迟队列;
  3. 已用 redis → 过期事件 + 定时任务兜底双保险。

七、灰度 & 监控

  • 灰度发布:按用户尾号或城市分批切换方案。
  • 监控指标
    • 取消成功率
    • mq 消息积压
    • redis 过期 qps
    • 定时任务扫描耗时

八、小结

一句话总结定时任务 简单但慢;延迟队列 实时但重;redis 过期 轻量但需兜底。

在实际落地中,可以 并行运行 两种方案(如延迟队列 + 兜底定时任务),通过配置开关灵活切换,确保业务永远在线。祝你的订单永不超卖!

以上就是springboot订单超时自动取消的三种主流实现方案的详细内容,更多关于springboot订单超时自动取消的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com