电商系统中有一个经典场景:用户下单后若长时间未支付,系统需要自动释放库存并取消订单。这个功能看似简单,实际涉及多个技术组件的协同工作。以30分钟未支付自动取消为例,我们需要考虑以下几个核心问题:
最直观的做法是启动一个定时任务,每隔固定时间(如1分钟)扫描所有待支付订单:
sql复制SELECT * FROM orders
WHERE status = '待支付'
AND create_time < NOW() - INTERVAL 30 MINUTE
优点:
缺点:
更优雅的方案是使用消息队列的延迟特性:
java复制// RocketMQ示例
Message message = new Message();
message.setDelayTimeLevel(16); // 对应30分钟延迟
producer.send(message);
优点:
缺点:
时间轮是一种高效的定时任务调度算法,Netty、Kafka等框架都有实现:
java复制HashedWheelTimer timer = new HashedWheelTimer();
timer.newTimeout(timeout -> {
// 处理超时订单
}, 30, TimeUnit.MINUTES);
优点:
缺点:
根据多年实战经验,我推荐采用分层架构:
第一层:Redis过期监听
bash复制SET order:1234 "pending" EX 1800 # 设置30分钟过期
第二层:RocketMQ延迟消息备份
java复制message.setDelayTimeLevel(16);
第三层:定时任务兜底(每天凌晨执行)
幂等设计:
java复制if(redis.setnx("lock:order:"+orderId, "1") == 1) {
// 处理订单
}
状态校验:
sql复制UPDATE orders SET status = '已取消'
WHERE order_id = ? AND status = '待支付'
补偿机制:
当订单量超过百万级时,建议采用分片策略:
java复制// 按订单ID哈希分片
int shard = orderId.hashCode() % 64;
executorService[shard].submit(task);
减少数据库交互次数:
sql复制UPDATE orders
SET status = '已取消'
WHERE status = '待支付'
AND create_time < ?
LIMIT 1000
建议在预发布环境验证:
消息丢失:
时间不准:
重复消费:
java复制if(redis.get("processed:"+msgId) != null) {
return;
}
建议监控:
根据系统规模选择方案:
| 系统规模 | 推荐方案 | 理由 |
|---|---|---|
| 初创项目 | 定时任务+Redis | 简单可靠 |
| 中型系统 | RabbitMQ死信队列 | 平衡复杂度 |
| 大型平台 | 时间轮+Kafka | 高性能 |
对于Java技术栈,我的实战推荐组合:
动态超时配置:
properties复制# 根据不同商品类型设置不同超时时间
order.timeout.food=1800
order.timeout.electronics=86400
用户提醒服务:
java复制// 15分钟未支付发送提醒
message.setDelayTimeLevel(8);
库存预占优化:
sql复制UPDATE inventory
SET locked = locked - 1
WHERE item_id = ?
在实际项目中,我们团队曾遇到过一个典型case:促销期间瞬时产生10万订单,最初设计的定时任务方案导致数据库CPU飙升至90%。后来改造为Redis过期监听+延迟消息队列的双重保障机制后,系统负载降至15%以下,且订单取消时间误差控制在±10秒内。