基于本文回答
0
评论

业务端在读取Redis主从架构的从节点数据时,意外读到了已过期的数据,原因是什么,该如何解决?

知识点图片

在Redis主从架构中,从节点读到已过期的数据是一个非常经典的“脏读”问题。导致这个问题的原因主要与Redis的版本以及服务器的时钟同步有关。

以下是详细的原因分析和相应的解决方案:


一、 原因分析

1. Redis 3.2 以下版本的历史遗留问题

在 Redis 3.2 版本之前,Redis 的过期键删除策略在主从架构下存在缺陷:

  • 主节点全权负责: Redis 的从节点不会主动去检查和删除过期的 key。只有当主节点通过定时扫描(Active)或被动访问(Lazy)发现 key 过期时,主节点才会执行删除,并向从节点发送一个 DEL 命令同步删除动作。
  • 从节点不校验 TTL: 在 3.2 之前的版本中,当业务端直接读取从节点时,从节点即使发现该 key 的 TTL 已经小于 0,也不会返回空,而是直接把还在内存中的脏数据返回给客户端,直到收到主节点发来的 DEL 命令才会真正删除并返回空。
  • 主从延迟放大问题: 由于主从同步是异步的,主节点生成 DEL 命令到从节点执行 DEL 命令存在网络延迟;加上主节点的后台定时清理并不总是能立刻发现过期键,这就导致了从节点会长时间返回已过期的数据。

2. 主从服务器系统时钟不一致(Redis 3.2 及以上版本的主要原因)

从 Redis 3.2 开始,官方修复了上述逻辑缺陷:在从节点读取数据时,会先检查该 key 的 TTL,如果发现已过期,虽然不会物理删除它(仍等待主节点的 DEL),但对客户端会返回 NULL
但为什么在 3.2 以上版本依然会遇到这个问题?核心原因是“时钟不一致”:

  • 当你在主节点执行 EXPIRE key 60(设置 60 秒过期)时,主节点在同步给从节点时,会将其转换为绝对时间戳命令:PEXPIREAT key <主节点当前绝对时间戳 + 60秒>
  • 如果从节点的系统时间比主节点的系统时间慢(比如慢了 10 秒),从节点收到这个绝对时间戳后,与自己的本地系统时间对比,就会认为这个 key 还要再过 70 秒才过期。
  • 因此,在这额外的 10 秒内,主节点认为 key 已经过期了,但从节点认为还没有,业务端在读取从节点时就会读到过期数据。

二、 解决方案

针对以上原因,可以采取以下几种解决方案(按推荐程度排序):

方案 1:配置 NTP 保证主从服务器时钟绝对同步(最关键)

如果是 Redis 3.2 以上版本,99% 的概率是因为时钟不同步引起的。

  • 做法: 在所有的 Redis 主从服务器上部署和配置 NTP(Network Time Protocol)服务,例如 chronydntpd,确保主节点和所有从节点的系统时间保持绝对一致(误差在毫秒级)。
  • 效果: 时钟同步后,从节点在利用 TTL 校验时就不会出现误判。

方案 2:升级 Redis 版本(针对老旧系统)

如果你当前使用的 Redis 版本低于 3.2(目前已经非常罕见,但老旧项目可能还在用 2.8 等版本)。

  • 做法: 将 Redis 升级到较新的稳定版本(如 6.x 或 7.x)。
  • 效果: 彻底解决从节点读取时不校验 TTL 的底层逻辑缺陷。

方案 3:业务强制读主节点(针对强一致性场景)

如果你的业务对数据的过期状态要求达到强一致性(例如分布式锁、防重放校验、支付状态等),主从架构的异步复制和从节点延迟是无法从物理层面上绝对消除的。

  • 做法: 在代码的 Redis 客户端配置中,将这类要求严格的数据的读请求,通过路由规则强制打到主节点(Master)上。
  • 效果: 避免了任何主从同步延迟带来的脏读问题,但会增加主节点的读压力。

方案 4:业务层兜底校验(冗余设计)

如果既想利用从节点分担读压力,又怕读到脏数据,可以在业务层面做兜底。

  • 做法: 在写入 Redis 的 Value 时,不仅设置 Redis 的 TTL,还在 Value 的业务数据结构内部包含一个 expire_timestamp(过期时间戳)。
  • 代码逻辑:
    python
    # 伪代码
    data = redis_slave.get(key)
    if data is not None:
        if current_system_time() > data.expire_timestamp:
            # 业务层面判定已过期,丢弃脏数据
            return None
        return data.value
  • 效果: 即使 Redis 出现了脏读,业务代码也能拦截下来,系统容错率极高。

总结建议:

立刻检查发生问题的 Redis 版本以及主从机器的系统时间(date 命令)。通常情况下,同步主从机器的时间就能解决绝大部分因为读取从节点导致的过期数据“诈尸”问题。

右滑查看面试常问