Redis从库读取主库创建且已过期的key
在 Redis 主从复制架构中,从库在读取一个主库创建且已过期但尚未被删除的键时,其行为取决于 Redis 的版本:
-
Redis 3.2 之前(有问题的行为):
- 从库会返回已过期的数据。 这是因为:
- 主库负责管理过期键。过期键的删除(无论是惰性删除还是定期删除)在主库上触发。
- 当主库删除一个过期键时,它会向所有从库发送一个
DEL
命令,从库才会删除该键。 - 关键点: 如果主库尚未执行删除操作(例如,该键刚过期,还没来得及被惰性删除或定期删除扫描到),那么这个过期的键及其值仍然存在于从库的内存中。
- 从库的视角: 从库不会主动检查键是否过期。它只忠实地保存和执行来自主库的复制流中的命令。如果主库没有发送
DEL
命令,从库就认为这个键仍然有效。 - 结果: 客户端连接到一个从库,请求这个过期的键,从库会直接返回键的值,就像它没有过期一样。这导致了读取到过期数据的不一致性问题。
- 从库会返回已过期的数据。 这是因为:
-
Redis 3.2 及之后(修复的行为 - 逻辑过期时间):
- 从库会返回
nil
(或者等效的空结果,如空列表/集合等)。 Redis 3.2 引入了一个重要的改进来解决这个问题:- 主库在向从库同步带有过期时间(
EXPIRE
/PEXPIRE
/SET ... EX
等)的键时,不再同步绝对过期时间戳,而是同步逻辑过期时间(相对时间)。 - 具体来说,主库同步的是
PEXPIREAT
命令,但参数被转换成了键的剩余生存时间(TTL)的毫秒数。例如,主库不是发送PEXPIREAT mykey 1718541235000
(绝对时间戳),而是计算这个绝对时间戳与当前时间的差值,发送PEXPIRE mykey 5000
(表示还剩 5000 毫秒过期)。
- 主库在向从库同步带有过期时间(
- 关键点: 从库接收到
PEXPIRE mykey <ttl>
命令后,会在本地重新计算这个键的绝对过期时间戳(基于从库自身的本地时钟加上收到的<ttl>
)。 - 结果:
- 当客户端在从库上读取一个键时,从库会使用自己本地存储的绝对过期时间戳来检查键是否过期。
- 如果从库判断该键已经过期(根据它自己计算出的绝对时间戳),它会在返回结果之前,将该键视为不存在,从而返回
nil
。 - 同时,从库不会主动删除这个键。它只会在读取时判断并返回空结果。实际的删除仍然需要等待:
- 主库的惰性删除或定期删除触发,然后主库发送
DEL
命令到从库。 - 或者,当该键在从库上被读取时(触发从库的惰性删除逻辑),从库会删除它并返回
nil
。但删除操作不会被传播回主库或其他从库。
- 主库的惰性删除或定期删除触发,然后主库发送
- 从库会返回
总结:
Redis 版本 | 从库读取主库创建且已过期但未删除的键 | 原因 |
---|---|---|
< 3.2 | 返回键的值 (过期数据) | 从库不检查过期,依赖主库发送 DEL 命令删除。主库未删,从库数据仍在。 |
>= 3.2 | 返回 nil (或等效空结果) | 主库同步相对TTL,从库基于自身时钟计算绝对过期时间戳并在读取时检查。如果过期则视作不存在。 |
重要补充说明:
- 主库删除是最终来源: 即使 Redis >= 3.2 的从库在读取时返回了
nil
,这个过期的键仍然物理存在于从库内存中,直到主库真正执行删除并同步DEL
命令,或者该键在从库上被访问触发了惰性删除。 - 主从延迟: 在主从复制存在延迟的情况下,主库删除键后发送
DEL
命令到达从库之前,从库可能已经根据本地计算的过期时间戳返回了nil
(>=3.2)或者还在返回过期数据(❤️.2)。复制延迟会加剧短暂的不一致窗口期。 - 从库的惰性删除: 当客户端在 Redis >= 3.2 的从库上读取一个它认为已过期的键时,它不仅返回
nil
,还会在执行读取操作后,在内部删除这个键(惰性删除)。这是从库本地清理过期键的一种方式。 - 一致性考虑: Redis 主从复制默认是最终一致性的。在过期键的处理上,尤其是在存在复制延迟时,不能保证在所有节点上严格同时过期并被删除。Redis 3.2+ 的改进极大地减少了读取到过期数据的可能性,但物理删除的传播仍然依赖主库的删除操作和复制流。
因此,对于现代 Redis 部署(>=3.2),你可以放心,从库在读取一个主库创建且已过期的键时,会返回 nil
,避免了返回陈旧数据。 如果你使用的是旧版本,则存在返回过期数据的风险。