一、技术方案组合(推荐)
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));
}
};
}
}
三、关键注意事项
- 幂等性设计
- 使用数据库行锁(SELECT FOR UPDATE)
- 更新时检查状态:
update orders set status='CLOSED' where id=#{id} and status='UNPAID'
- 分布式锁(Redis或Redisson)
- 数据一致性
- 采用最终一致性方案
- 重要操作记录日志(可考虑事件溯源)
- 使用本地事务表保证关键操作
- 性能优化
- 分库分表处理海量订单
- 补偿任务使用分页查询(limit 1000)
- 异步线程池处理关闭操作
- 监控报警
- 记录消息消费延迟情况
- 监控定时任务执行情况
- 设置订单关闭失败报警阈值
- 兜底方案
- 每日对账任务检查异常订单
- 提供管理后台手动关闭接口
- 保留72小时内的订单关闭日志
四、架构图
sql
+-------------+ +---------------+ +---------------+
| Order | | Message | | Redis |
| Service +---->+ Queue +---->+ (Optional) |
+-----+-------+ +-------+-------+ +-------+-------+
| | |
| | |
+-----v-------+ +-------v-------+ +-------v-------+
| Scheduled | | Consumer | | Key |
| Task | | Service | | Expiration |
+-------------+ +---------------+ +---------------+
↓ ↓ ↓
+-------------------------------------------------------+
| Database & Lock Service |
+-------------------------------------------------------+
五、扩展优化建议
- 动态关闭时间
- 在订单表中增加close_time字段
- 延迟消息时间根据业务规则动态计算
- 关闭前提醒
- 在到期前5分钟发送提醒通知
- 使用多级延迟消息(25分钟提醒+30分钟关闭)
- 流量控制
- 消息消费端采用令牌桶限流
- 数据库更新操作批量处理
- 多级缓存
- 使用本地缓存+Redis缓存订单状态
- 减少数据库查询压力
这种组合方案可以保证在日均百万订单量的情况下,实现:
- 99.95%的订单能在30±1分钟内关闭
- 系统资源消耗降低60%以上
- 人工干预需求减少90%以上