5.Redis过期机制与内存淘汰策略
5.Redis过期机制与内存淘汰策略
Redis 是怎么删除过期 key 的?
Redis 支持对 key 设置过期时间,但并不是在 key 到期的那一刻就立即删除,而是通过设计合理的“过期删除策略”来在性能和内存之间做平衡。定时删除理论上最及时,key 一到期就删除,但实现成本高,需要为每个 key 创建定时器,反而拖累性能。惰性删除则是当客户端访问一个 key 时,Redis 会先检查其是否过期,若过期就删除并返回空值。这种方式极大节省了 CPU 开销,但会出现“没人访问就不删除”的情况,最终可能积压大量垃圾数据。
为了解决这个问题,Redis 引入了定期删除机制,它会在后台周期性扫描部分已设置过期时间的 key,清理其中已过期的部分。这种机制同样是“采样式”的,每次随机抽取若干个 key 进行检测,并控制 CPU 使用率,比如 Redis 会抽取 20 个 key,如果超过 25% 已过期则继续扫描,直到比例低于阈值。
所以 Redis 并没有选用一种理想但不现实的方式,而是通过惰性 + 定期的组合策略,在 CPU 占用、内存释放、实时性之间找到一个工程上可接受的平衡点。
Redis 有几种内存回收策略?
Redis 的内存控制机制是通过配置 maxmemory
来启用的,一旦内存使用超过上限,就需要执行内存回收策略。不同策略应对的场景和风险也不同。
maxmemory支持各单位:
maxmemory 1024 (默认字节)
maxmemory 1024KB
maxmemory 1024MB
maxmemory 1204GB
首先,有一种极端情况是不进行数据淘汰,即 noeviction
,这在 Redis 3.0 后成为默认策略,表现是:一旦内存不够,新写入操作将被直接拒绝。这种策略适合“数据不可丢”的业务,但在高写入量场景下风险很高。
其余策略可以分为两大类。一类是针对设置了过期时间的 key,如 volatile-lru
(只淘汰设置了过期时间中最近最少使用的)、volatile-ttl
(优先淘汰快过期的 key)等,适合缓存类数据;另一类策略作用于所有 key,比如 allkeys-lru
、allkeys-random
,甚至 allkeys-ttl
,更适合数据全量都可丢弃的场景。
内存回收是什么时候发起?
Redis 的内存淘汰逻辑是内嵌在命令执行路径里的。每次客户端发送读写命令,Redis 会进入核心函数 processCommand
,在执行真正的读写之前,它会调用内存检查逻辑,判断当前内存使用是否超过了配置的 maxmemory
。
如果检测到超限,Redis 就会调用内存释放逻辑 freeMemoryIfNeeded
,这个函数会根据当前配置的淘汰策略,尝试回收一部分 key 来腾出空间。由于这一过程是嵌套在命令执行中的,因此并不是一个后台定时任务,而是随操作触发的“即时检查”机制。
这种设计的好处是避免了不必要的资源开销,只在真正可能产生写入、导致内存超限时才尝试释放资源,同时也能快速响应内存压力,而不会等定时任务延迟处理。
介绍下 Redis 的 LRU 回收算法
传统 LRU 算法需要为每个 key 维护精确的访问时间顺序,一般是通过链表或红黑树实现,但这在 Redis 中是不可接受的,因为 Redis 是内存数据库,额外维护链表结构会显著增加内存占用。
为了解决这个问题,Redis 设计了一种近似的 LRU 算法,核心思想是采样 + 淘汰池优化。每当需要回收内存时,Redis 会随机从 key 空间中抽取一组样本(默认是 5 个),然后找出这组中最久未被访问的 key 进行淘汰。如果淘汰后内存仍不足,就继续采样下一轮。
Redis 在 3.0 之后又引入了淘汰候选池机制:每轮采样得到的 key 会放入一个小池子中,后续新的采样如果优于池中最差者,才会替换入池,最终从池中淘汰最久未使用的 key。这有效提升了 LRU 的淘汰质量,同时保持了较低的内存开销。
Redis 的 LRU 算法是标准的吗?为什么不用标准的?
标准的 LRU 需要用双向链表或红黑树来精确维护 key 的访问顺序,每次读写都要更新结构,代价高昂。在 Redis 的极端高并发场景下,这种方案会拖累整体性能,甚至引发锁竞争和 CPU 上升。
因此,Redis 选择了更轻量的采样淘汰方案:每轮随机选出一批 key,记录它们的访问时间,然后淘汰其中最久未访问者。采样轮次不固定,直到内存足够或淘汰失败。为了增强淘汰效果,还引入了“淘汰池机制”,维持一个候选集合,动态替换,优中择劣。
这种设计虽然不是百分百准确的 LRU,但在实际使用中,已经能非常有效地识别“冷数据”,实现了淘汰效率与系统性能的平衡。
什么是 LFU 算法,为什么 Redis 要引入 LFU?
相比 LRU 关注最后一次访问时间,LFU 更关注的是访问频率。这在某些场景下至关重要:比如一些业务的热点 key 在一分钟内被访问几千次,但一段时间没用就被 LRU 淘汰了。这种淘汰是不合理的,因为该 key 的价值远超其他数据。
Redis 在 4.0 中引入了 LFU 算法,并通过精简的计数方式记录 key 的访问频率。每个 key 都带有一个 8 位的计数器,这个计数在访问时按一定概率增加,并定期衰减,以避免计数膨胀。这样可以在较低成本下判断哪些 key 真的是常用而非偶尔突发。
在 maxmemory-policy
中可以选择 allkeys-lfu
或 volatile-lfu
,用于控制是否对所有 key 或仅有过期时间的 key 应用 LFU 策略。
