Redis 3.2之前从节点不检查过期键,直接返回旧值;3.2+版本引入逻辑时钟,在读取时主动判断并返回nil,但依赖主节点DEL命令同步及主从时钟一致。

Redis从节点不维护自己的过期判断逻辑(3.2之前)
在 Redis 3.2 之前,从节点读取数据时完全不检查键是否已过期。哪怕主节点早已把 key 标记为过期、甚至触发了惰性删除,从节点仍会原样返回旧值——它压根不执行任何过期校验。
这不是 bug,是设计如此:早期版本的从节点只做“数据搬运工”,所有过期决策都交给主节点统一控制。结果就是,客户端一读从库,就可能拿到逻辑上已失效的数据。
- 典型现象:
GET key在从节点返回非空值,但主节点返回nil - 根本原因:从节点没有自己的过期时钟,也不运行惰性删除逻辑
- 修复方式:必须升级到 Redis 3.2+,该版本起从节点引入了“逻辑时钟”机制,在读取时主动判断并过滤过期键
主节点的 DEL 命令同步存在延迟
即使在 3.2+,从节点也不是靠自己删过期键,而是等主节点发出 DEL 命令再执行。而这个命令的生成依赖主节点的两种删除策略:
- 惰性删除:只有访问时才检查,不访问就不触发
DEL - 定期删除:每 100ms 随机抽样检查,默认只扫描 20 个 key,漏检率高
这意味着:一个键在主节点到达 TTL 后,可能几秒甚至几十秒后才真正被 DEL,这段时间内从节点既没收到命令,又不会自己删,数据就“卡”在过期未删状态。
尤其在写少读多、且 key 访问稀疏的场景下,这种延迟更明显——你设了 60 秒 TTL,实际从节点上残留时间可能是 65 秒、80 秒,甚至更久。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
EXPIRE/PEXPIRE 的时间基准在主节点上锁定
EXPIRE 和 PEXPIRE 设置的是“从命令执行那一刻起,往后多少秒/毫秒过期”。这个“执行时刻”是主节点本地时间戳,从节点不会重新计算。
如果主从服务器系统时间不同步(比如相差 2 秒),就会出现偏差:
- 主节点认为某 key 已过期,发
DEL;但从节点本地时间还差 2 秒,它收到命令前可能已提前返回旧值 - 更隐蔽的是:若主节点用
EXPIREAT传 Unix 时间戳,而主从时区不一致(如主在 UTC+8、从在 UTC+0),时间戳解析后直接偏移 8 小时
所以别只盯着 Redis 配置,ntpd 或 chrony 时间同步服务必须在主从节点上稳定运行,且误差建议控制在 50ms 内。
RMapCache 与原生 Hash 的 TTL 行为差异
如果你用的是 Redisson,问题可能根本不在 Redis 协议层——而在于你选错了封装类。例如:
-
RMap:不支持 TTL,调用expire()无效,remainTimeToLive()永远返回-1 -
RMapCache:靠客户端定时轮询模拟过期,网络抖动或客户端挂掉就失效 -
RMapCacheV2:用 Redis 原生命令(如SET key val EX 300)实现,行为与直连 Redis 一致
很多“TTL 不同步”其实是应用层误用了 RMap,以为它和 SET ... EX 一样有自动过期能力。查日志时注意看实际发出的 Redis 命令,而不是看 Java 方法名。

















