Java订单超时案例如何处理

wen java案例 29

本文目录导读:

Java订单超时案例如何处理

  1. 定时任务轮询(基础方案)
  2. 延时消息队列(推荐方案)
  3. Redis 过期监听(轻量级方案)
  4. 时间轮算法(高性能方案)
  5. 综合方案(生产推荐)
  6. 最佳实践建议

针对 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());
            }
        });
    }
}

最佳实践建议

  1. 首选方案延时消息队列(RocketMQ/RabbitMQ)—— 兼顾精度和可靠性
  2. 次选方案Redis过期监听 —— 适合中小型项目
  3. 避免方案:纯定时任务轮询 —— 误差大,数据库压力大
  4. 完整保障:主方案 + Redis辅助 + 定时兜底

选择哪种方案主要看你的业务规模、精度要求和团队技术栈。

抱歉,评论功能暂时关闭!