Redis采用的是定期删除+懒惰删除策略。
(1)定期删除策略
Redis会将每个设置了过期时间的key放入到一个独立的字典里,默认每100ms进行一次过期扫描:
1.随机抽取20个key
2.删除这20个key中过期的key
3.如果过期的key比例超过1/4,重复步骤1,继续删除。
问题1:为什么不扫描所有的key
由于redis是单线程,而且为了防止每次扫描过期的 key 比例都超过 1/4,导致不停循环卡死线程,Redis 为每次扫描添加了上限时间,默认是 25ms。
问题2:为什么 Redis 每次扫描的上限时间是 25ms,还会出现上面的情况?
因为 Redis 是单线程,每个请求处理都需要排队,而且由于 Redis 每次扫描都是 25ms,也就是每个请求最多 25ms,100 个请求就是 2500ms。
如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力。
(2)从库的过期策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在。
(3)懒惰删除策略
删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟。不过如果删除的 key 是一个非常大的对象,比如一个包含了千万元素的 hash,又或者在使用 FLUSHDB 和 FLUSHALL 删除包含大量键的数据库时,那么删除操作就会导致单线程卡顿。
redis 4.0 引入了 lazyfree 的机制,它可以将删除键或数据库的操作放在后台线程里执行, 从而尽可能地避免服务器阻塞。
(4)unlink
unlink指令,能对删除操作进行懒处理,丢给后台线程异步回收内存。
(5)flush
flushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作。Redis 4.0 同样给这两个指令也带来了异步化,在指令后面增加 async 参数就可以将操作交给后台线程慢慢进行运行。
(6)异步队列
如果主线程要回收某些大且过期的对象,会将这个key的内存回收操作包装成一个任务,放进异步队列,后台线程会从这个异步队列中取任务,任务队列中,主线程和异步线程可同时操作,因此是一个线程安全的队列。
(7)更多异步删除点
slave-lazy-flush 从库接受完 rdb 文件后的 flush 操作
lazyfree-lazy-eviction 内存达到 maxmemory 时进行淘汰
lazyfree-lazy-expire key 过期删除
lazyfree-lazy-server-del rename 指令删除 destKey
(8)内存淘汰机制
Redis 的内存占用会越来越高。Redis 为了限制最大使用内存,提供了 redis.conf 中的配置参数 maxmemory。当内存超出 maxmemory,Redis 提供了几种内存淘汰机制让用户选择,配置 maxmemory-policy:
1.noeviction:当内存超出 maxmemory,写入请求会报错,但是删除和读请求可以继续。(不推荐)
2.allkeys-lru:当内存超出 maxmemory,在所有的 key 中,移除最少使用的key。只把 Redis 既当缓存是使用这种策略。(推荐)。
3.allkeys-random:当内存超出 maxmemory,在所有的 key 中,随机移除某个 key。
4.volatile-lru:当内存超出 maxmemory,在设置了过期时间 key 的字典中,移除最少使用的 key。把 Redis 既当缓存,又做持久化的时候使用这种策略。
5.volatile-random:当内存超出 maxmemory,在设置了过期时间 key 的字典中,随机移除某个key。
6.volatile-ttl:当内存超出 maxmemory,在设置了过期时间 key 的字典中,优先移除 ttl 小的。
(9)LRU算法
实现 LRU 算法除了需要 key/value 字典外,还需要附加一个链表,链表中的元素按照一定的顺序进行排列。当空间满的时候,会踢掉链表尾部的元素。当字典的某个元素被访问时,它在链表中的位置会被移动到表头。所以链表的元素排列顺序就是元素最近被访问的时间顺序。
Redis使用的是随机LRU算法,Redis 为每一个 key 增加了一个24 bit的字段,用来记录这个 key 最后一次被访问的时间戳。
注意 Redis 的 LRU 淘汰策略是懒惰处理,也就是不会主动执行淘汰策略,当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行 LRU 淘汰算法。这个算法就是随机采样出5(默认值)个 key,然后移除最旧的 key,如果移除后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory 为止。