在前两篇中,我们用定时任务巡逻、DelayQueue 倒计时,各有利弊。这一篇,我们不自己查时间,也不自己做队列,而是让 Redis 来提醒我们:谁超时了。
微信支付系列相关的文章:
Redis 提供的 Key 过期机制,本就是用来解决“到点就失效”的场景。搭配过期事件监听功能(Keyspace Notification),我们就能轻松构建一个无轮询、系统开销极低的订单自动关单机制。
实现原理
我们将每个订单的标识作为 Key,设置一个 15 分钟的过期时间,一旦订单未支付,Redis 在时间一到就会删除这个 Key。此时通过 Redis 的 Key 过期事件通知,我们就可以在后台实时监听到这个“订单已超时”事件,从而执行关单逻辑。
sequenceDiagram
participant 用户
participant 应用服务
participant Redis
participant 数据库
用户->>应用服务: 提交订单
应用服务->>数据库: 创建订单记录
应用服务->>Redis: SET order:12345 "pending" EX 900
Redis-->>应用服务: OK
alt 用户支付订单
用户->>应用服务: 完成支付
应用服务->>Redis: DEL order:12345
应用服务->>数据库: 更新订单状态为已支付
else 订单超时未支付
Redis->>应用服务: 发布key过期通知(order:12345 expired)
应用服务->>数据库: 检查订单状态
数据库-->>应用服务: 订单状态为未支付
应用服务->>数据库: 更新订单状态为已取消
应用服务->>用户: 发送订单取消通知
end
实现步骤
1、开启 Redis Keyspace 通知
Redis 默认不监听过期事件,需要在配置文件或运行时开启:
# redis.conf 文件中加入
notify-keyspace-events Ex
# 或在运行时动态设置
127.0.0.1:6379> CONFIG SET notify-keyspace-events Ex
参数说明:
E: 启用 Key 事件通知(Event)x: 包含过期事件(expired)
2、设置订单 Key + 过期时间
当用户创建订单时:
// 设置一个 15 分钟过期的 Redis key
String redisKey = "order:" + orderId;
redisTemplate.opsForValue().set(redisKey, "", 15, TimeUnit.MINUTES);
Key 的值可以为空,主要是利用它的 TTL 功能。
3、监听 Redis 过期事件
可以使用 Lettuce 或 Jedis 实现订阅监听,这里以 Spring Boot + Lettuce 示例:
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
@Autowired
private OrderService orderService;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
if (expiredKey.startsWith("order:")) {
Long orderId = Long.valueOf(expiredKey.replace("order:", ""));
log.info("监听到订单超时事件,准备关闭订单: {}", orderId);
orderService.closeOrder(orderId);
}
}
}
4、注册监听容器
@Configuration
public class RedisListenerConfig {
@Bean
public RedisMessageListenerContainer listenerContainer(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
return container;
}
@Bean
public RedisKeyExpirationListener keyExpirationListener(RedisMessageListenerContainer container) {
return new RedisKeyExpirationListener(container);
}
}
Redis Key 过期监听是一种极轻量、精度尚可的实现方式,非常适合对架构复杂度要求不高的业务,特别是中小型电商平台。它像一个给每个订单挂上了 Redis 自带的“倒计时闹钟” ,时间一到就通知你该干事了。
Redis过期通知差不多是实时的,实际误差可能在秒级,基本上满足大部分业务需求。这种实现方式相对来说也比较简单,相对于轮训来说会降低数据库的压力,而且搭配上Redis持久化订单状态,重启也不会丢任务
这里也有一些优化方案,比如在执行订单关闭的时候,可以查询一下数据库,确认一下订单的状态,再进行订单关闭的操作,防止出现错误。
另外一个防止出现漏单的情况,可以增加一些定时任务,比如每天执行一次,查询数据库中最近24小时的订单,确认一下订单的状态是否正常。这些兜底操作在生产环境中,可以保证一些系统的可靠性。