使用redis zset + mq实现。
其实这个方案就是长延迟用 Redis,短延迟用 RocketMQ,既保证了高精度触发,又突破了 RocketMQ 延迟等级的限制。
实现思路
- 下单时
- 把订单 ID 和到期时间(
orderExpireTime)存到 Redis 的 ZSet,score 就是到期时间的时间戳(毫秒)。
- 把订单 ID 和到期时间(
- 定时扫描 Redis
- 每隔一段时间(比如 1 分钟)扫描
score <= 当前时间的订单 ID。 - 把这些订单 ID 从 ZSet 删除。
- 发 RocketMQ 延迟消息(如果还需要短延迟,比如多等几秒做缓冲)。
- 每隔一段时间(比如 1 分钟)扫描
- RocketMQ 消费者
- 收到消息执行订单关闭逻辑(更新订单状态、回滚库存等)。
- 幂等处理
- 确保订单关闭是幂等的,避免重复执行。
Redis 数据结构
# ZSet key: order_timeout
ZADD order_timeout 1734144000000 orderId_123 # score 是到期时间戳
下单时写入 Redis
public void saveOrderToRedis(String orderId, long expireMillis) {
String key = "order_timeout";
long expireAt = System.currentTimeMillis() + expireMillis; // 过期时间戳
redisTemplate.opsForZSet().add(key, orderId, expireAt);
}
定时任务扫描到期订单并发 MQ 消息
@Scheduled(fixedRate = 60000) // 每分钟扫描一次
public void scanAndSendToMQ() {
String key = "order_timeout";
long now = System.currentTimeMillis();
Set<String> dueOrders = redisTemplate.opsForZSet().rangeByScore(key, 0, now);
if (dueOrders != null && !dueOrders.isEmpty()) {
// 删除这些订单
redisTemplate.opsForZSet().remove(key, dueOrders.toArray());
// 发到 RocketMQ 让消费者执行关闭
for (String orderId : dueOrders) {
sendCloseOrderMQ(orderId);
}
}
}
优化点
扫描频率:可以按分钟、5分钟等频率调整,看业务精度需求
批量发送:到期订单多时,批量发 MQ 消息
高可用:定时任务用分布式任务调度(如 XXL-JOB、Quartz 集群模式)
防重:订单关闭时先判断状态是否已关闭
作者:张三 创建时间:2025-08-13 15:00
最后编辑:张三 更新时间:2025-11-28 10:00
最后编辑:张三 更新时间:2025-11-28 10:00