项目中,如何实现订单到期关闭?

一、技术方案组合(推荐)

1. 延迟消息 + 定时任务补偿

  • 核心流程使用消息队列延迟消息
  • 定时任务做兜底补偿
  • 结合分布式锁保证幂等性

2. Redis过期监听(可选补充)

  • 作为辅助手段提供快速响应
  • 不单独作为核心方案

二、详细实现步骤

1. 下单时发送延迟消息

java 复制代码
// 订单创建成功后发送延迟消息
public void createOrder(Order order) {
    orderService.save(order);
    
    // 发送30分钟延迟消息(RocketMQ示例)
    Message<OrderCloseMsg> message = MessageBuilder.withPayload(new OrderCloseMsg(order.getId()))
            .setHeader(RocketMQHeaders.DELAY_TIME_LEVEL, 3) // 对应30分钟级别
            .build();
    rocketMQTemplate.send(message);
}

2. 消息消费者处理

java 复制代码
@RocketMQMessageListener(topic = "ORDER_CLOSE_TOPIC", consumerGroup = "order_close_group")
public class OrderCloseConsumer implements RocketMQListener<OrderCloseMsg> {
    
    @Override
    @Transactional
    public void onMessage(OrderCloseMsg message) {
        // 获取分布式锁
        String lockKey = "order_close_lock:" + message.getOrderId();
        try {
            if (redisLock.tryLock(lockKey, 30, TimeUnit.SECONDS)) {
                Order order = orderService.getById(message.getOrderId());
                
                // 检查订单状态
                if (order != null && order.getStatus() == OrderStatus.UNPAID) {
                    closeOrder(order);
                }
            }
        } finally {
            redisLock.unlock(lockKey);
        }
    }

    private void closeOrder(Order order) {
        // 1. 更新订单状态
        order.setStatus(OrderStatus.CLOSED);
        orderService.updateById(order);
        
        // 2. 释放库存(分布式事务方案)
        inventoryService.unlockStock(order.getSkuId(), order.getQuantity());
        
        // 3. 记录操作日志
        orderLogService.logOperation(order.getId(), "AUTO_CLOSE");
    }
}

3. 定时补偿任务(Spring Scheduler示例)

java 复制代码
@Scheduled(cron = "0 0/10 * * * ?") // 每10分钟执行
public void compensateCloseOrders() {
    // 查询30-40分钟前的未支付订单(留出10分钟缓冲)
    Date endTime = DateUtils.addMinutes(new Date(), -30);
    Date startTime = DateUtils.addMinutes(endTime, -10);
    
    List<Order> unpaidOrders = orderMapper.selectUnpaidOrders(startTime, endTime);
    
    unpaidOrders.forEach(order -> {
        // 使用线程池异步处理
        asyncExecutor.execute(() -> {
            // 复用消息消费者的关闭逻辑
            OrderCloseMsg msg = new OrderCloseMsg(order.getId());
            new OrderCloseConsumer().onMessage(msg);
        });
    });
}

4. Redis过期补充方案(可选)

java 复制代码
// 订单创建时设置Redis key
redisTemplate.opsForValue().set(
    "order_close:" + orderId, 
    "1", 
    30, TimeUnit.MINUTES
);

// 配置Redis过期监听
@Configuration
public class RedisKeyExpirationConfig {
    
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(expirationListener(), new PatternTopic("__keyevent@0__:expired"));
        return container;
    }

    @Bean
    public MessageListener expirationListener() {
        return (message, pattern) -> {
            String expiredKey = new String(message.getBody());
            if (expiredKey.startsWith("order_close:")) {
                String orderId = expiredKey.split(":")[1];
                // 提交到线程池处理(避免阻塞监听线程)
                asyncExecutor.execute(() -> processExpiredOrder(orderId));
            }
        };
    }
}

三、关键注意事项

  1. 幂等性设计
  • 使用数据库行锁(SELECT FOR UPDATE)
  • 更新时检查状态:update orders set status='CLOSED' where id=#{id} and status='UNPAID'
  • 分布式锁(Redis或Redisson)
  1. 数据一致性
  • 采用最终一致性方案
  • 重要操作记录日志(可考虑事件溯源)
  • 使用本地事务表保证关键操作
  1. 性能优化
  • 分库分表处理海量订单
  • 补偿任务使用分页查询(limit 1000)
  • 异步线程池处理关闭操作
  1. 监控报警
  • 记录消息消费延迟情况
  • 监控定时任务执行情况
  • 设置订单关闭失败报警阈值
  1. 兜底方案
  • 每日对账任务检查异常订单
  • 提供管理后台手动关闭接口
  • 保留72小时内的订单关闭日志

四、架构图

sql 复制代码
+-------------+     +---------------+     +---------------+
|  Order      |     |  Message      |     |  Redis        |
|  Service    +---->+  Queue        +---->+  (Optional)   |
+-----+-------+     +-------+-------+     +-------+-------+
      |                     |                     |
      |                     |                     |
+-----v-------+     +-------v-------+     +-------v-------+
|  Scheduled  |     |  Consumer     |     |  Key          |
|  Task       |     |  Service      |     |  Expiration   |
+-------------+     +---------------+     +---------------+
       ↓                     ↓                     ↓
+-------------------------------------------------------+
|                  Database & Lock Service              |
+-------------------------------------------------------+

五、扩展优化建议

  1. 动态关闭时间
  • 在订单表中增加close_time字段
  • 延迟消息时间根据业务规则动态计算
  1. 关闭前提醒
  • 在到期前5分钟发送提醒通知
  • 使用多级延迟消息(25分钟提醒+30分钟关闭)
  1. 流量控制
  • 消息消费端采用令牌桶限流
  • 数据库更新操作批量处理
  1. 多级缓存
  • 使用本地缓存+Redis缓存订单状态
  • 减少数据库查询压力

这种组合方案可以保证在日均百万订单量的情况下,实现:

  • 99.95%的订单能在30±1分钟内关闭
  • 系统资源消耗降低60%以上
  • 人工干预需求减少90%以上
相关推荐
盖世英雄酱581367 分钟前
同事说缓存都用redis啊,数据不会丢失!真的吗?
redis·后端·面试
L2ncE1 小时前
双非计算机自救指南(找工作版)
后端·面试·程序员
cdg==吃蛋糕2 小时前
solr自动建议接口简单使用
后端·python·flask
Joseit2 小时前
基于 Spring Boot实现的图书管理系统
java·spring boot·后端
{⌐■_■}2 小时前
【go】什么是Go语言的GPM模型?工作流程?为什么Go语言中的GMP模型需要有P?
java·开发语言·后端·golang
IT杨秀才3 小时前
LangChain框架入门系列(5):Memory
人工智能·后端·langchain
程序猿chen3 小时前
JVM考古现场(二十四):逆熵者·时间晶体的永恒之战
java·jvm·git·后端·程序人生·java-ee·改行学it
AronTing3 小时前
单例模式:确保唯一实例的设计模式
java·javascript·后端
AronTing3 小时前
模板方法模式:定义算法骨架的设计模式
java·后端·面试
AronTing3 小时前
迭代器模式:统一数据遍历方式的设计模式
java·后端·面试