本文目录导读:

针对 Java 订单超时处理,常见的方案有以下几种,我按从简单到复杂、从低精度到高精度的顺序来介绍:
定时任务轮询(基础方案)
最简单的方案,通过定时任务扫描数据库。
@Component
public class OrderTimeoutTask {
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void processTimeoutOrders() {
// 查询超时未支付订单
List<Order> timeoutOrders = orderMapper.selectTimeoutOrders(
LocalDateTime.now().minusMinutes(30)
);
// 处理超时订单
timeoutOrders.forEach(order -> {
order.setStatus(OrderStatus.CANCELED);
orderMapper.updateById(order);
// 退还库存
inventoryService.releaseStock(order.getSkuId(), order.getQuantity());
// 发送通知
notificationService.sendTimeoutNotice(order.getUserId(), order.getOrderNo());
});
}
}
优点: 实现简单
缺点: 存在时间误差(最大1分钟),数据库压力大
延时消息队列(推荐方案)
使用 RocketMQ 或 RabbitMQ 的延时消息功能。
RocketMQ 实现
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单
Order order = createOrder(orderDTO);
// 2. 发送延时消息,30分钟后检查
Message<String> message = MessageBuilder
.withPayload(order.getOrderNo())
.build();
// 延时等级:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
// 延时30分钟对应第16级
rocketMQTemplate.syncSend(
"order-timeout-topic",
message,
3000,
16 // 延时等级
);
}
}
@Component
@RocketMQMessageListener(
topic = "order-timeout-topic",
consumerGroup = "order-timeout-group"
)
public class OrderTimeoutConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String orderNo) {
// 检查订单状态
Order order = orderMapper.selectByOrderNo(orderNo);
if (order != null && order.getStatus() == OrderStatus.PENDING) {
// 取消订单
cancelOrder(order);
}
}
}
RabbitMQ 实现
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void createOrder(OrderDTO orderDTO) {
// 创建订单
Order order = createOrder(orderDTO);
// 发送延时消息
rabbitTemplate.convertAndSend(
"order-exchange",
"order.timeout",
order.getOrderNo(),
message -> {
message.getMessageProperties()
.setDelay(30 * 60 * 1000); // 30分钟
return message;
}
);
}
}
@Component
@RabbitListener(queues = "order-timeout-queue")
public class OrderTimeoutListener {
@RabbitHandler
public void handleTimeout(String orderNo) {
//处理逻辑
}
}
优点: 精度高(毫秒级),解耦,可靠
缺点: 需要引入消息中间件
Redis 过期监听(轻量级方案)
利用 Redis 的键过期通知功能。
@Service
public class OrderService {
@Autowired
private StringRedisTemplate redisTemplate;
public void createOrder(OrderDTO orderDTO) {
Order order = createOrder(orderDTO);
// 设置订单超时key,30分钟过期
String key = "order:timeout:" + order.getOrderNo();
redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.MINUTES);
}
}
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
if (expiredKey.startsWith("order:timeout:")) {
String orderNo = expiredKey.replace("order:timeout:", "");
// 检查并取消订单
Order order = orderMapper.selectByOrderNo(orderNo);
if (order != null && order.getStatus() == OrderStatus.PENDING) {
cancelOrder(order);
}
}
}
}
注意: Redis 过期通知不保证100%可靠,建议作为辅助方案或对精度要求不高的场景。
时间轮算法(高性能方案)
适用于高并发场景,内存中维护时间轮。
@Component
public class OrderTimeoutWheel {
private HashedWheelTimer timer = new HashedWheelTimer(
new DefaultThreadFactory("order-timeout"),
100, // tick duration
TimeUnit.MILLISECONDS,
512 // ticks per wheel
);
public void addTimeoutOrder(Order order, long delayMs) {
timer.newTimeout(
timeout -> handleOrderTimeout(order),
delayMs,
TimeUnit.MILLISECONDS
);
}
private void handleOrderTimeout(Order order) {
// 处理超时逻辑
if (order.getStatus() == OrderStatus.PENDING) {
cancelOrder(order);
}
}
}
优点: 性能极高,适合高并发
缺点: 需自行处理持久化,服务重启后丢失
综合方案(生产推荐)
将上述方案组合使用,实现可靠性和性能的平衡。
@Service
public class OrderTimeoutService {
// 1. Redis记录超时时间(快速检查)
// 2. 消息队列确保可靠性
// 3. 定时任务作为兜底
public void createOrderWithTimeout(OrderDTO orderDTO) {
Order order = createOrder(orderDTO);
String orderNo = order.getOrderNo();
// 1. 发送延时消息(主方案)
sendTimeoutMessage(orderNo, 30, TimeUnit.MINUTES);
// 2. Redis记录(快速过滤)
redisTemplate.opsForValue().set(
"order:timeout:" + orderNo,
String.valueOf(System.currentTimeMillis() + 30*60*1000),
35, TimeUnit.MINUTES // 多给5分钟buffer
);
}
// 兜底定时扫描(每5分钟扫描一次)
@Scheduled(fixedRate = 300000)
public void recoveryScan() {
// 扫描超时订单(提前5分钟的超时时间,避免与正常流程冲突)
List<Order> timeoutOrders = orderMapper.selectTimeoutOrders(
LocalDateTime.now().minusMinutes(35)
);
timeoutOrders.forEach(order -> {
if (order.getStatus() == OrderStatus.PENDING) {
cancelOrder(order);
log.warn("兜底方案取消订单:{}", order.getOrderNo());
}
});
}
}
最佳实践建议
- 首选方案:延时消息队列(RocketMQ/RabbitMQ)—— 兼顾精度和可靠性
- 次选方案:Redis过期监听 —— 适合中小型项目
- 避免方案:纯定时任务轮询 —— 误差大,数据库压力大
- 完整保障:主方案 + Redis辅助 + 定时兜底
选择哪种方案主要看你的业务规模、精度要求和团队技术栈。