Spring Boot 订单超时自动取消的 3 种主流实现方案

Spring Boot 订单超时自动取消的 3 种主流实现方案

关键词:Spring Boot、订单超时、延迟任务、RabbitMQ、Redis、定时任务

在电商、外卖、票务等业务中,“下单后若 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 {@Beanpublic CustomExchange delayExchange() {Map<String, Object> args = Map.of("x-delayed-type", "direct");return new CustomExchange("order.delay", "x-delayed-message", true, false, args);}@Beanpublic Queue cancelQueue() {return QueueBuilder.durable("order.cancel.queue").build();}@Beanpublic 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 minreturn msg;});}
}
3.3 消费并取消
@Component
@RabbitListener(queues = "order.cancel.queue")
public class CancelConsumer {private final OrderService orderService;@RabbitHandlerpublic 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 随意,这里用 idredisTemplate.opsForValue().set("order:" + order.getId(),String.valueOf(order.getId()),Duration.ofMinutes(30));}
}
3.2 监听过期事件
@Configuration
public class RedisListenerConfig {@Beanpublic 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 过期 轻量但需兜底。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89718.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89718.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

0401聚类-机器学习-人工智能

文章目录一 无监督学习什么是无监督学习&#xff1f;核心特点&#xff1a;无监督学习的主要类型1. 聚类分析 (Clustering)2. 降维 (Dimensionality Reduction)3. 关联规则学习 (Association Rule Learning)4. 异常检测 (Anomaly Detection)5. 密度估计 (Density Estimation)二 …

基础神经网络模型搭建

nn 包提供通用深度学习网络的模块集合&#xff0c;接收输入张量&#xff0c;计算输出张量&#xff0c;并保存权重。通常使用两种途径搭建 PyTorch 中的模型&#xff1a;nn.Sequential和 nn.Module。 nn.Sequential通过线性层有序组合搭建模型&#xff1b;nn.Module通过__init__…

基于单片机出租车计价器设计

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 本设计实现了一种基于单片机的智能化出租车计价系统。系统以单片机为核心处理器&#xff0c;集成…

134. Java 泛型 - 上限通配符

文章目录134. Java 泛型 - 上限通配符 (? extends T)**1. 什么是上限通配符 (? extends T)&#xff1f;****2. 为什么使用 ? extends T&#xff1f;****3. 示例&#xff1a;使用 ? extends T 进行数据读取****✅ 示例 1&#xff1a;计算数值列表的总和****4. 注意事项&…

【1】YOLOv13 AI大模型-可视化图形用户(GUI)界面系统开发

【文章内容适用于任意目标检测任务】【GUI界面系统不局限于YOLOV13&#xff0c;主流YOLO系列模型同样适用】本文以车辆行人检测为背景&#xff0c;介绍基于【YOLOV13模型】和【AI大模型】的图形用户&#xff08;GUI&#xff09;界面系统的开发。助力大论文实现目标检测模型的应…

小程序常用api

1. wx.request - 发起网络请求 用于向服务器发送 HTTP 请求&#xff0c;获取数据或提交表单。 // 示例&#xff1a;GET 请求获取数据 wx.request({url: https://api.example.com/data, // 替换为实际 API 地址method: GET,success: (res) > {console.log(请求成功, res.da…

PaliGemma 2-轻量级开放式视觉语言模型

PaliGemma 2是轻量级开放式视觉语言模型 (VLM)&#xff0c;灵感源自 PaLI-3&#xff0c;基于 SigLIP 视觉模型和 Gemma 语言模型等开放式组件。PaliGemma 同时接受图片和文本作为输入&#xff0c;并且可以回答有关图片的详细问题和背景信息。PaliGemma 2 提供 30 亿、100 亿和 …

腾讯云云服务器深度介绍

以下是围绕腾讯云云服务器&#xff08;CVM&#xff09;的详细介绍与推荐文章&#xff0c;结合其核心优势、应用场景及技术特性&#xff0c;为不同用户群体提供参考&#xff1a; &#x1f680; 一、产品定位与核心价值 腾讯云云服务器&#xff08;Cloud Virtual Machine, CVM&a…

Ceph OSD.419 故障分析

Ceph OSD.419 故障分析 1. 问题描述 在 Ceph 存储集群中&#xff0c;OSD.419 无法正常启动&#xff0c;系统日志显示服务反复重启失败。 2. 初始状态分析 观察到 OSD.419 服务启动失败的系统状态&#xff1a; systemctl status ceph-osd419 ● ceph-osd419.service - Ceph obje…

MySQL持久化原理及其常见问题

目录 MySQL刷盘原理 脏页和干净页 MySQL出现短暂的堵塞SQL现象 情况分析 应对措施 数据库表中数据删除原理 删除表中数据数据库空间大小不会改变 情况分析 应对措施 MySQL刷盘原理 一般主要分为两个步骤 内存更新和 redo log 记录是同一事务修改的两个必要操作&#…

VSCode中Cline无法正确读取终端的问题解决

出现的问题是&#xff1a;Cline 无法正确读取终端输出。 Shell Integration Unavailable Cline won’t be able to view the command’s output. Please update VSCode (CMD/CTRL Shift P → “Update”) and make sure you’re using a supported shell: zsh, bash, fish, o…

scalelsd 笔记 线段识别 本地部署 模型架构

ant-research/scalelsd | DeepWiki https://arxiv.org/html/2506.09369?_immersive_translate_auto_translate1 https://gitee.com/njsgcs/scalelsd https://github.com/ant-research/scalelsd https://huggingface.co/cherubicxn/scalelsd 模型链接&#xff1a; https…

Python, C ++开发个体户/个人品牌打造APP

个体户/个人品牌打造APP开发方案&#xff08;Python C&#xff09;一、技术选型与分工1. Python- 核心场景&#xff1a;后端API开发、数据处理、内容管理、第三方服务集成&#xff08;如社交媒体分享、支付接口&#xff09;。- 优势&#xff1a;开发效率高&#xff0c;丰富的库…

SQLAlchemy 常见问题笔记

文章目录SQLAlchemy Session对象如何操作数据库SQLAlchemy非序列化对象如何返回1.问题分析2.解决方案方法1&#xff1a;使用 Pydantic 响应模型&#xff08;推荐&#xff09;方法2&#xff1a;手动转换为字典&#xff08;简单快速&#xff09;方法3&#xff1a;使用 SQLAlchemy…

Shell脚本-uniq工具

一、前言在 Linux/Unix 系统中&#xff0c;uniq 是一个非常实用的文本处理命令&#xff0c;用于对重复的行进行统计、去重和筛选。它通常与 sort 搭配使用&#xff0c;以实现高效的文本数据清洗与统计分析。无论是做日志分析、访问频率统计&#xff0c;还是编写自动化脚本&…

氛围编码(Vice Coding)的工具选择方式

一、前言 在写作过程中&#xff0c;我受益于若干优秀的博客分享&#xff0c;它们给予我宝贵的启发&#xff1a; 《5分钟选对AI编辑器&#xff0c;每天节省2小时开发时间让你早下班&#xff01;》&#xff1a;https://mp.weixin.qq.com/s/f0Zm3uPTcNz30oxKwf1OQQ 二、AI编辑的…

[硬件电路-57]:根据电子元器件的受控程度,可以把电子元器件分为:不受控、半受控、完全受控三种大类

根据电子元器件的受控程度&#xff0c;可将其分为不受控、半受控、完全受控三大类。这种分类基于元器件的工作状态是否需要外部信号&#xff08;如电压、电流、光、热等&#xff09;的主动调控&#xff0c;以及调控的精确性和灵活性。以下是具体分类及实例说明&#xff1a;一、…

基于Pytorch的人脸识别程序

人脸识别原理详解人脸识别是模式识别和计算机视觉领域的重要研究方向&#xff0c;其目标是从图像或视频中识别出特定个体的身份。现代人脸识别技术主要基于深度学习方法&#xff0c;特别是卷积神经网络 (CNN)&#xff0c;下面从多个维度详细解析其原理&#xff1a;1. 人脸识别的…

ubuntu 开启ssh踩坑之旅

文章目录确认当前用户为普通用户 or root命令使用ssh还是sshd服务名称的由来apt update和apt upgrade的关系apt upgrade报错&#xff1a;“E: 您在 /var/cache/apt/archives/ 上没有足够的可用空间”开启ssh步骤错误排查查看日志修改sshd_config文件允许防火墙通过22端口确认当…

力扣:动态规划java

sub07 线性DP - O(1) 状态转移2_哔哩哔哩_bilibili 跳楼梯 class Solution {public int climbStairs(int n) {if (n < 1) {return 1; // 处理边界情况}int[] dp new int[n 1]; // 创建长度为n1的数组&#xff0c;比方说跳二级楼梯dp[0] 1; // 初始值设定dp[1] 1;for (…