过期策略
Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key。所谓惰性删除就是在客户端访问这个 key 的时候,Redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除时零散处理。
定时扫描策略
Redis 默认会每秒进行 10 次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
- 从过期字典中随机 20 个 key;
- 删除这 20 个 key 中已经过期的 key;
- 如果过期的 key 比率超过了 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
如果一个大型的 Redis 实例中所有的 key 在同一时间过期,Redis 会持续扫描过期字典(循环多次),直到过期字典中过期的 key 变得稀疏,或等待时间超过 25ms,这就会导致线上读写请求出现明显的卡顿现象,甚至大量的链接因为超时而关闭,业务端就会出现很多异常。而且这是我们还无法从 Redis 的 showlog 中看到慢查询记录,因为慢查询指的时逻辑处理过程慢,不包含等待时间。
所以业务开发人员一定要注意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,从而分散过期处理的压力。
从库的过期策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里面增加一条 del 指令,然后同步到所有的从库,从库通过执行这条 del指令来删除过期的 key。
因为指令同步时异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致。
内存淘汰策略
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换(swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
在生产环境中我们是不允许 Redis 出现交换行为,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略(maxmemory-policy)来让用户自己觉醒该如何腾出新的空间以继续提供读写服务。
noeviction
不会继续服务器写请求(DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
volatile-lru
尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
volatile-ttl
跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
volatile-random
跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
allkeys-lru
区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
allkeys-random
跟上面一样,不过淘汰的策略是随机的 key。
小结
volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰。如果我们只是拿 Redis 做缓存,那应该使用 allkeys-xxx,客户端写缓存时不必携带过期时间;如果我们还想同时使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,这样可以保留没有设置过期时间的 key,它们是永久的 key 不会被 LRU 算法淘汰。