本文目录导读:

针对 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辅助 + 定时兜底
选择哪种方案主要看你的业务规模、精度要求和团队技术栈。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播