【关单机制系列 3】Redis 过期监听:让 Redis 来帮你看时间

127 阅读3分钟

在前两篇中,我们用定时任务巡逻、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 过期事件

可以使用 LettuceJedis 实现订阅监听,这里以 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小时的订单,确认一下订单的状态是否正常。这些兜底操作在生产环境中,可以保证一些系统的可靠性。