前言
之前 白馨(陌陌-技术保障部存储工程师 )在Redis技术交流群里,总结了一下Redis从2.8~4.0关于过期键相关的fix记录,非常有帮助,但有些东西未尽详细,本文将进行详细说明。
先从一个问题来看,运行环境如下:
Redis: 2.8.19
db0:keys=10000000,expires=10000000
主从结构
从下图中可以看到,在从节点get hello非空,在主节点get hello为空,之后从节点get hello为空,经排查主从同步offset基本正常,但出现了主从不一致。

原因先不说,本文来探讨下Redis2.8-4.0版本迭代中,针对过期键的fix,看看能不能找到答案。
一、过期功能回顾
当你执行了一条setex命令后,Redis会向内部的dict和expires哈希结构中分别插入数据:
二、Redis过期键的删除策略:当键值过期后,Redis是如何处理呢?综合考虑Redis的单线程特性,有两种策略:惰性删除和定时删除。
1.惰性删除策略:
在每次执行key相关的命令时,都会先从expires中查找key是否过期,下面是3.0.7的源码(db.c):
下面是读写key相关的入口:
三、过期读写问题Redis过期删除策略带来的问题。我们只从用户操作的角度来讨论。
1、过期键读操作
下面是Redis 2.8~4.0过期键读操作的fix记录
(1) Redis2.8主从不一致
2.8中的读操作中都先调用lookupKeyRead函数:

3.2并未解决exists这个命令的问题,虽然它也是个读操作。之后的4.0.11中问题才得以解决.
2、过期键写操作
在具体说这个问题之前,我们先说一下可写从库的使用场景。
(1).主从分离场景中,利用从库可写执行耗时操作提升性能。
作者在https://redis.io/topics/replication 中提到过:
For example computing slow Set or Sorted set operations and storing them into local keys is an use case for writable slaves that was observed multiple times.
在 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4 举了一个更具体的例子:
For instance imagine having slaves replicating certain Sets keys from the master. When accessing the data on the slave, we want to peform intersections between
such Sets values. However we don’t want to intersect each time: to cache the intersection for some time often is a good idea.
也就是说在读写分离的场景中,可以使用过期键的机制将从库作为一个缓存,去缓存从库上耗时操作的结果,提升整体性能。
(2). 迁移数据时,需要先将从库设置为可写。
比如下列场景:线上Redis服务正常,但可能遇到一些硬件的情况,需要对该机器上的Redis主从集群迁移。迁数据的方式就是搭建一个新的主从集群,让新主成为旧主的从。
进行如下操作:
•(1)主(旧主)从(新主)同步,rdb传输完毕90s之后,设置从库(新主)可写。
•(2)在主库(旧主)完全没有业务连接后,从库(新主)执行slaveof no one。
这种场景下,为了保证数据完全同步,并且尽量减少对业务的影响,就会先设置从库可写。
接着我们来做一个测试:
3.2版本主库执行的操作,主库的过期键正常过期。

3.2版本可写从库执行以下操作,从库的过期键并不会过期。

4.0rc3版本可写从库执行以下操作,从库的过期键却能够过期。

其实可写从库过期键问题包含两个问题:
•(1)从库中的过期键由主库同步过来的,过期操作由主库执行(未变更过)。
•(2)从库中的过期键的设置是从库上操作的。
redis4.0rc3之前,存在过期键泄露的问题。当expire直接在从库上操作,这个key是不会过期的。作者也在https://redis.io/topics/replication 提到过:
However note that writable slaves before version 4.0 were incapable of expiring keys with a time to live set. This means that if you use EXPIRE or other commands that set a maximum TTL for a key, the key will leak, and while you may no longer see it while accessing it with read commands, you will see it in the count of keys and it will still use memory. So in general mixing writable slaves (previous version 4.0) and keys with TTL is going to create issues.
过期键泄露问题在https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4中得到了解决。
四.总结
1、针对过期键读操作
(1) Redis2.8主从不一致
(2) Redis3.2-rc1主从除exists之外都一致: https://github.com/antirez/redis/commit/06e76bc3e22dd72a30a8a614d367246b03ff1312
(3) Redis4.0.11主从一致:
https://github.com/antirez/redis/commit/32a7a2c88a8b8cca8119b849eee7976b8ada8936
2、针对过期键的写操作:
Redis2.8~4.0都只返回物理结果。
3、从库中对key执行expire操作,key不会过期。
Redis4.0 rc3解决从库中设置的过期键不过期问题 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4
4、如果slave非读写分离、上述迁移使用,基本本文问题不会出现。还有就是Redis 4非常靠谱,后面也会有文章介绍相关内容。(付磊)
本篇文章到此结束,如果您有相关技术方面疑问可以联系我们技术人员远程解决,感谢大家支持本站!