过期策略
-
定时删除
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。 -
惰性删除
惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。 定时删除是集中处理,惰性删除是零散处理。 -
定期删除
Redis 默认会每 100ms 进行一次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。- 从过期字典中随机 20 个 key
- 删除这 20 个 key 中已经过期的 key
- 如果过期的 key 比率超过 1/4,那就重复步骤 1
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
对比
定时删除对内存友好,能够在 key 过期后立即从内存中删除,但是对 CPU 不友好,如果过期键较多会占用 CPU 对一些时间
惰性删除对 CPU 友好,只有在键用到的时候才会进行检查,对于很多用不到的 key 不用浪费时间进行检查,但是对内存不友好,过期 key 如果一直没用到就会一直在内存中,内存就得不到释放,从而造成内存泄漏。
定期删除可以通过限制操作时长和频率减少删除对 CPU 的影响,同时也能释放过期 key 占用的内存;但是频率和时长不太好控制,执行频繁了和定时一样占用 CPU,执行太少和惰性删除又一样对内存不好。
一般会使用组合策略 惰性删除 和 定期删除 组合使用。
从库的过期策略
从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。
内存淘汰策略
为了限制最大使用内存,Redis 提供了配置参数 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。
LRU 算法
实现 LRU 算法除了需要 key/value 字典外,还需要附加一个链表,链表中的元素按照一定的顺序进行排列。当空间满的时候,会踢掉链表尾部的元素。当字典的某个元素被访问时,它在链表中的位置会被移动到表头。所以链表的元素排列顺序就是元素最近被访问的时间顺序。
位于链表尾部的元素就是不被重用的元素,所以会被踢掉。位于表头的元素就是最近刚刚被人用过的元素,所以暂时不会被踢。
近似 LRU 算法
Redis 使用的是一种近似 LRU 算法,它跟 LRU 算法还不太一样。之所以不使用 LRU 算法,是因为需要消耗大量的额外的内存,需要对现有的数据结构进行较大的改造。
近似 LRU 算法则很简单,在现有数据结构的基础上使用随机采样法来淘汰元素,能达到和 LRU 算法非常近似的效果。Redis 为实现近似 LRU 算法,它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳。
当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行一次 LRU 淘汰算法。这个算法也很简单,就是随机采样出 5(可以配置) 个 key,然后淘汰掉最旧的 key,如果淘汰后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory 为止。
采样按照 maxmemory-policy 的配置,如果是 allkeys 就是从所有的 key 字典中随机,如果是 volatile 就从带过期时间的 key 字典中随机。每次采样多少个 key 看的是 maxmemory_samples 的配置,默认为 5。