bigywater.tech
Redis · 28

过期 Key 对复制的影响

起一主一从两个Redis实例,禁用主库后台过期扫描,验证从库对一个已过期的key是真的删除了数据,还是只在读取时假装它不存在。

从库不自主删除
只在读取时屏蔽,物理数据保留
主库是权威
过期判定只能由主库做出
显式DEL传播
从库靠复制流里的DEL命令才会物理删除

做的事:起一主一从,观察过期是怎么在两边发生的

建立一对主从复制关系,让主库禁用后台过期扫描,只能靠被动过期。

操作本机起两个Redis实例:master(19379)和replica(19380) REPLICAOF让replica跟随master 主库: DEBUG SET-ACTIVE-EXPIRE 0 -- 禁用主库的后台清理 主库: SET repltest v1 PX 300 sleep 1秒(远超300ms TTL),完全不访问这个key

验证:1秒后,主库和从库上这个key都还物理存在

用DEBUG OBJECT绕开正常的过期屏蔽逻辑,检查物理存储。

结果主库: DEBUG OBJECT repltest → :Value at:0x80c290d88 refcount:1 encoding:embstr ...(还在!) 从库: DEBUG OBJECT repltest → :Value at:0xc91054518 refcount:1 encoding:embstr ...(从库也还在!)

验证了:主库因为禁用了后台扫描,这个早已过期的key还占用着内存——这一点前面文章验证过。真正新的发现是:从库上这个key也还物理存在,哪怕从库有自己独立的系统时钟,理论上完全可以自己判断"这个key的TTL已经过了"。

验证:从库的GET返回空,但不会自己删除数据

从库是怎么在"不删除"和"不返回过期数据"之间做到两全的。

结果从库: GET repltest → 返回空(客户端读不到) 从库: DEBUG OBJECT repltest → :数据依然物理存在(跟上一步矛盾?不,这才是关键)

关键点在这里:从库不会自主删除这个已经"看起来过期"的key——它只是在处理 GET 这类读命令时,临时判断一下"这个key的TTL是不是已经过了",过了就假装它不存在(返回空),但底层数据原封不动地留着,不会真的执行删除。

验证:只有主库发来DEL,从库才会真的删除

触发主库的被动过期,观察DEL命令是怎么传播到从库的。

结果主库: GET repltest → 触发主库自己的被动过期,返回空 sleep 0.3秒(等复制流同步过去) 从库: DEBUG OBJECT repltest → :ERR no such key(被物理删除了!)
完整的逻辑链条是:主库是过期这件事唯一的权威来源——不管是被动触发(访问时发现过期)还是主动触发(后台扫描),一旦主库判定一个key过期了,它会向所有从库发送一条显式的 DEL 命令(走正常的复制流),从库收到这条DEL才会物理删除数据。从库自己永远不会因为"我的系统时钟也觉得这个key该过期了"就主动删除任何数据——这是为了保证主从数据的一致性:如果从库自己独立判断过期,一旦主从时钟有轻微误差,或者主库因为某种原因(比如本文验证过的禁用后台扫描)迟迟没有真正过期某个key,从库却自己先删了,主从数据就会出现不一致。用"只听主库指挥"这个规则,彻底避免了这类问题。

分步演示:过期key是怎么在主从之间传播的

每一步都是本文上面用一主一从两个Redis实例跑出来的原始结果。

从库只屏蔽读取,从不自主删除,只听主库的DEL
这一步新验证的状态

    本文用到的工具

    • 本机起的两个Redis 8.10.1实例,master(19379) + replica(19380),REPLICAOF建立复制
    • DEBUG SET-ACTIVE-EXPIRE / DEBUG OBJECT:绕开正常读取路径检查物理存储
    京ICP备2026067502号-1京公网安备11010502063487号