Redis过期策略
因为内存是有限的,如果一直只进不出就会导致 OOM,所以一般情况下,缓存中的数据都会被设置一个过期时间,以此缓解内存的消耗。其次,一些特殊的应用场景也会用到过期时间,比如短信验证码的缓存。所以,Redis 需要删除缓存中的这些过期数据。
一、如何判断缓存数据是否过期?
Redis 通过一个叫做过期字典(可以看作是 hash表)来保存数据过期的时间,每当我们对一个 key 设置了过期时间时,Redis 会把该 key 带上过期时间存储到这个过期字典表中。
过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。其数据结构如下:
字典实际上是哈希表,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找。当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:
- 如果不在,则正常读取键值;
- 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。
二、常见的过期策略
1、定时删除:在设置 key 的过期时间时,同时创建一个定时事件,当到达过期时间时,由事件处理器自动执行 key 的删除操作。
优点:
- 可以保证过期 key 会被尽快删除,也就是内存可以被尽快地释放。因此,定时删除对内存是最友好的。
缺点:
- 在过期 key 比较多的情况下,删除过期 key 可能会占用相当一部分 CPU 时间,在内存不紧张但 CPU 时间紧张的情况下,将 CPU 时间用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。所以,定时删除策略对 CPU 不友好。
2、惰性删除:只有每次在缓存中取出 key 的时候才对数据进行过期检查,如果过期则删除该 key。
优点:
- 因为每次访问时,才会检查 key 是否过期,所以此策略只会使用很少的系统资源,因此,惰性删除策略对 CPU 时间最友好。
缺点:
- 如果一个 key 已经过期,而这个 key 又仍然保留在数据库中,那么只要这个过期 key 一直没有被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费。所以,惰性删除策略对内存不友好。
3、定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
优点:
- 通过限制删除操作执行的时长和频率,来减少删除操作对 CPU 的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效占用。
缺点:
- 内存清理方面没有定时删除效果好,同时没有惰性删除使用的系统资源少。
- 难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好;如果执行的太少,那又和惰性删除一样了,过期 key 占用的内存不会及时得到释放。
三、Redis中的过期策略
上述每一种过期策略都有优缺点,仅使用某一个策略都不能满足实际需求。所以, Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
1、Redis 是怎么实现惰性删除的?
Redis 的惰性删除策略由 db.c 文件中的 expireIfNeeded 函数实现。
惰性删除流程图如下:
2、Redis 是怎么实现定期删除的?
定期删除的具体做法为,每隔一段时间「随机」从缓存中取出一定数量的 key 进行检查,并删除其中的过期key。
所以,"间隔时间多长?" "随机数量是多少?"在删除过程尤为重要。
(1)间隔时间
在 Redis 中,默认每秒进行 10 次过期检查一次数据库,此配置可通过 Redis 的配置文件 redis.conf 进行配置,配置键为 hz 它的默认值是 hz 10。
注:每次检查缓存中的 key 时,并不是遍历过期字典中的所有 key,而是从缓存中随机抽取一定数量的 key 进行过期检查。
(2)随机数量
定期删除的实现在 expire.c 文件下的 activeExpireCycle 函数中,其中随机抽查的数量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定义的,它是写死在代码中的,数值是 20。
也就是说,数据库每轮抽查时,会随机选择 20 个 key 判断是否过期。
定期删除流程图:
总结:
Redis 默认同时开启定期删除和惰性删除两种过期策略。
定期删除其实并不会立即释放内存,而是把这些键标记为“已过期”,并放入一个专门的链表中。然后,在 Redis 的内存使用率达到一定阈值时,Redis 会对这些“已过期”的键进行一次内存回收操作,释放被这些键占用的内存空间。
需要注意的是,即使 Redis 进行了内存回收操作,也不能完全保证被删除的内存空间会立即被系统回收。
一般来说,这些被删除的内存空间会被操作系统标记为“可重用的内存”,等待被重新分配。因此,即使 Redis 进行了内存回收操作,也并不能保证 Redis 所占用的内存空间会立即释放给操作系统。
而惰性删除则是在键被访问时进行过期检查,如果过期了则删除键并释放内存。
四、Redis 中有一批 key 瞬间过期,为什么其它 key 的读写效率会降低?
通过上述分析,当 Redis 执行定期删除时,此时正好有一批 key 都过期了需要删除,那么这个时候就会导致接下来其他 key 的读写效率降低。
因为 Redis 在执行定期删除过期 key 是在单线程模型中的主线程执行的,此时如果有大量的 key 需要删除,那么就得等删除命令执行完毕后才能处理其他 key 的读写操作,也就出现了业务访问延时增大的问题。
如何解决这个问题呢?
- 随机过期时间:如果可能的话,可以将键的过期时间随机分布,避免大量 key 过期时间相同,而不是在同一时间点过期。这样可以分散过期键删除的压力,避免大规模的键同时过期。同时也能避免缓存雪崩的问题。
- 使用被动删除:被动删除在每次访问的时候才会去删除key,并不会定时删除,不太容易发生大量key同时被删除的情况。但是被动删除可能会导致内存占用比较多。
参考:
- Redis 过期删除策略和内存淘汰策略有什么区别? | 小林coding