在 Java 生态中处理下单超时取消问题,分析几种主流解决方案,同时讲述下每种方案的适用场景和优缺点
主流方案的技术实现
适用场景:高并发、分布式系统
推荐中间件:RocketMQ(支持任意精度延迟消息)
java// 订单创建时发送延迟消息
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
private static final String TIMEOUT_TOPIC = "ORDER_TIMEOUT_TOPIC";
@Transactional
public Order createOrder(CreateOrderRequest request) {
// 1. 创建订单
Order order = orderRepository.save(buildOrder(request));
// 2. 发送30分钟超时消息
Message<String> message = MessageBuilder.withPayload(order.getOrderId())
.setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, "3") // 延迟级别3=30分钟
.build();
rocketMQTemplate.syncSend(TIMEOUT_TOPIC, message);
return order;
}
}
// 消费者处理超时订单
@Component
@RocketMQMessageListener(
topic = "ORDER_TIMEOUT_TOPIC",
consumerGroup = "order_timeout_group")
public class OrderTimeoutConsumer implements RocketMQListener<String> {
@Autowired
private OrderService orderService;
@Override
@Transactional
public void onMessage(String orderId) {
// 检查并处理超时订单
orderService.processTimeoutOrder(orderId);
}
}
// 超时订单处理逻辑
@Service
public class OrderService {
// 使用乐观锁防止重复处理
private static final String LOCK_PREFIX = "order_timeout:";
@Autowired
private RedissonClient redissonClient;
public void processTimeoutOrder(String orderId) {
RLock lock = redissonClient.getLock(LOCK_PREFIX + orderId);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
if (order.getStatus() == OrderStatus.UNPAID) {
// 执行取消逻辑
cancelOrder(order);
log.info("订单超时取消成功: {}", orderId);
}
}
} finally {
lock.unlock();
}
}
private void cancelOrder(Order order) {
// 1. 更新订单状态
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
// 2. 释放库存
inventoryService.releaseStock(order);
// 3. 记录操作日志
logService.recordCancelLog(order);
}
}
优点:
缺点:
在回头看下,这点缺点也算是问题?
适用场景:单机高并发、低延迟要求
java// 基于Netty HashedWheelTimer实现
@Component
public class OrderTimeoutManager {
private final HashedWheelTimer timer = new HashedWheelTimer(
new NamedThreadFactory("order-timeout-timer"),
100, // 100ms一个tick
TimeUnit.MILLISECONDS,
512 // 时间轮大小
);
private final ConcurrentMap<String, Timeout> timeoutTasks = new ConcurrentHashMap<>();
public void scheduleTimeout(String orderId, long delay, TimeUnit unit) {
TimerTask task = new TimerTask() {
@Override
public void run(Timeout timeout) {
processTimeout(orderId);
}
};
Timeout timeout = timer.newTimeout(task, delay, unit);
timeoutTasks.put(orderId, timeout);
}
public void cancelTimeout(String orderId) {
Timeout timeout = timeoutTasks.remove(orderId);
if (timeout != null) {
timeout.cancel();
}
}
private void processTimeout(String orderId) {
// 处理超时逻辑(需持久化存储)
orderService.processTimeoutOrder(orderId);
timeoutTasks.remove(orderId);
}
}
// 集成到订单服务
@Service
public class OrderService {
@Autowired
private OrderTimeoutManager timeoutManager;
public Order createOrder(CreateOrderRequest request) {
Order order = orderRepository.save(buildOrder(request));
// 30分钟后超时
timeoutManager.scheduleTimeout(order.getOrderId(), 30, TimeUnit.MINUTES);
return order;
}
public void onPaymentSuccess(String orderId) {
// 支付成功时取消超时任务
timeoutManager.cancelTimeout(orderId);
}
}
优点:
缺点:
适用场景:轻量级应用、已有Redis基础设施
java@Component
public class RedisOrderTimeoutQueue {
private static final String KEY = "order:timeout:queue";
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedissonClient redissonClient;
// 添加订单到延迟队列
public void addOrder(String orderId, long timeoutMinutes) {
double score = System.currentTimeMillis() + (timeoutMinutes * 60 * 1000);
redisTemplate.opsForZSet().add(KEY, orderId, score);
}
// 取消订单超时
public void cancelOrder(String orderId) {
redisTemplate.opsForZSet().remove(KEY, orderId);
}
// 处理超时订单(定时任务调用)
public void processExpiredOrders() {
long now = System.currentTimeMillis();
// 获取所有已超时的订单
Set<String> orderIds = redisTemplate.opsForZSet().rangeByScore(KEY, 0, now);
for (String orderId : orderIds) {
RLock lock = redissonClient.getLock("lock:order_timeout:" + orderId);
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 原子移除
Long removed = redisTemplate.opsForZSet().remove(KEY, orderId);
if (removed != null && removed > 0) {
orderService.processTimeoutOrder(orderId);
}
}
} finally {
lock.unlock();
}
}
}
}
// 定时任务配置
@Configuration
@EnableScheduling
public class ScheduleConfig {
@Autowired
private RedisOrderTimeoutQueue timeoutQueue;
@Scheduled(fixedRate = 5000) // 每5秒执行一次
public void checkOrderTimeout() {
timeoutQueue.processExpiredOrders();
}
}
优点:
缺点:
适用场景:复杂调度需求、已有调度基础设施
java// XXL-JOB 处理器
@Component
public class OrderTimeoutJobHandler extends IJobHandler {
@Autowired
private OrderRepository orderRepository;
@Override
public ReturnT<String> execute(String param) {
// 分片处理
int shardIndex = getShardIndex();
int shardTotal = getShardTotal();
// 查询待处理订单(按分片规则)
List<Order> orders = orderRepository.findTimeoutOrders(
shardIndex,
shardTotal,
PageRequest.of(0, 100)
);
for (Order order : orders) {
processOrder(order);
}
return SUCCESS;
}
private void processOrder(Order order) {
// 使用CAS更新状态
int updated = orderRepository.updateOrderStatus(
order.getOrderId(),
OrderStatus.UNPAID.getValue(),
OrderStatus.CANCELLED.getValue()
);
if (updated > 0) {
// 执行取消逻辑
inventoryService.releaseStock(order);
}
}
}
sqlSELECT * FROM orders
WHERE status = 'UNPAID'
AND created_time < DATE_SUB(NOW(), INTERVAL 30 MINUTE)
AND MOD(order_id % 1024, #{shardTotal}) = #{shardIndex}
LIMIT 100
优点:
缺点:
方案 | 实时性 | 可靠性 | 分布式支持 | 复杂度 | 适用场景 |
---|---|---|---|---|---|
消息队列延迟消息 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | 高并发生产环境(推荐方案) |
时间轮算法 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | ⭐ | 单机高性能应用 |
Redis ZSet 延迟队列 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | 轻量级应用,已有Redis |
分布式调度框架 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 复杂调度需求,已有调度中心 |
数据库定时扫描 | ⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | 小型系统(不推荐生产环境) |
多层防御机制
这种组合方案能兼顾实时性、可靠性和可维护性,适合大多数生产环境。具体选型应根据团队技术栈、系统规模和业务需求灵活调整。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!