Redis淘汰策略

Redis 整理(过期删除+内存淘汰+LRU/LFU/TTL)

一、Redis 过期 Key 的三种删除策略

什么时候清理过期键?Redis 针对设置了 expire 过期时间的 key,有三种删除策略,生产环境默认组合使用「惰性删除 + 定期删除」

1. 定时删除

主动删除,Redis 未采用

  • 原理:给 key 设置过期时间时,创建定时器,时间一到立刻删除 key。
  • 优点:内存最友好,过期键能马上释放内存。
  • 缺点:极度消耗 CPU;海量 key 同时过期时,定时器任务扎堆执行,阻塞 Redis 主线程,降低吞吐量。

2. 惰性删除

被动删除,Redis 默认开启

  • 原理:过期 key 不会主动清理,只有客户端访问该 key时,Redis 先校验过期时间,已过期则直接删除并返回 nil。
  • 优点:CPU 零额外开销,只在读写命中时做判断,性能无损耗。
  • 缺点:内存不友好;若过期 key 长期无人访问,会永久占用内存,形成内存泄漏。

3. 定期删除

主动兜底,Redis 默认开启

  • 原理:Redis 后台定时任务 serverCron (默认 100ms 执行一次),随机抽取一批带过期时间的 key 批量校验、删除。
    • 执行逻辑:每次随机抽 20 个过期 key,删除全部过期 key;若本轮过期 key 占比超 25%,重复扫描;单次扫描限时 25ms,防止阻塞主线程。
  • 优点:折中方案,平衡 CPU 与内存,兜底清理长期无人访问的过期键。
  • 缺点:随机抽样存在漏删可能,无法一次性清理全部过期 key。

4. 提炼后:Redis 实际过期清理机制

Redis 采用 惰性删除 + 定期删除 组合:

  1. 惰性删除:每次读写 key 时实时清理已过期 key;
  2. 定期删除:后台定时抽样扫描,兜底清理冷过期 key;
  3. 内存淘汰策略:内存打满写入新数据时,会额外清理过期 / 冷数据,三重保障释放内存。

二、Redis 8 种内存淘汰策略

内存达到 maxmemory 阈值时触发

触发时机

Redis 配置 maxmemory 限制最大内存,当写入新 key 导致内存超出阈值时,执行内存淘汰策略释放空间。

分类两大维度

  1. allkeys 系列:淘汰全部 key(包含永久无过期 key)
  2. volatile 系列:仅淘汰设置过过期时间的 key,永久业务数据保留

完整 8 种策略详解

  1. noeviction (默认策略)

    不淘汰任何 key,写入新数据直接返回 OOM 报错;读操作正常执行,适合存放不可丢失的核心业务数据。

  2. allkeys-lru (生产最常用)

    全局所有 key,淘汰 最近最少使用 的 key;通用缓存场景首选。

  3. volatile-lru
    仅在带过期时间的 key 中,淘汰最久未访问的 key;适合区分永久数据 + 临时缓存的混合存储。

  4. allkeys-lfu (Redis4.0 + 推荐)

    全局所有 key,淘汰 访问频次最低 的 key;解决 LRU 历史热点缓存污染问题。

  5. volatile-lfu
    仅过期 key 范围内,淘汰访问次数最少的 key。

  6. allkeys-random

    全局随机删除任意 key,性能最好,但缓存命中率极低,极少使用。

  7. volatile-random

    仅在过期 key 中随机删除,无冷热区分。

  8. volatile-ttl

    仅过期 key 中,优先淘汰剩余存活时间最短 、马上过期的 key;适合短期临时缓存场景。

生产选型总结

  • 纯缓存、无永久业务数据:allkeys-lfu / allkeys-lru
  • 混合存储(永久核心数据 + 临时缓存):volatile-lru / volatile-lfu
  • 临时短期过期缓存:volatile-ttl
  • 核心数据不可丢失:noeviction(需手动扩容)

三、LRU / LFU / TTL 核心概念与底层实现

基础定义

  1. LRU(Least Recently Used)最近最少使用

    核心思想:时间维度,淘汰最久没有被访问的数据;假设近期访问的数据未来大概率再次使用。

  2. LFU(Least Frequently Used)最不经常使用

    核心思想:频次维度,淘汰一段时间内访问次数最少的数据;区分真正热点与偶尔访问的临时数据。

  3. TTL(Time To Live)剩余过期时间

    仅针对带过期时间的 key,优先清理马上到期的数据,不关心访问行为。

1. 标准 LRU 理想实现(面试手写 LRU 缓存考点)

要求:get/put 操作时间复杂度 O(1)

数据结构组合:HashMap + 双向链表

![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%27256%27%20height=%27192%27/%3e)image

LRU哈希+双向链表结构

  • HashMap:存储 key → 链表节点映射,实现 O (1) 快速查找节点;
  • 双向链表:维护访问时序
    • 链表头部:最新访问 / 新增节点(热数据)
    • 链表尾部:最久未访问节点(淘汰目标)

执行流程

  1. get 命中 key:哈希找到节点,删除原位置,移动到链表头部;
  2. put 新增 key:容量未满直接插入头部;容量满,删除链表尾部节点,同时删除 HashMap 映射;
  3. put 更新 key:更新 value,节点移至头部。

2. Redis 近似 LRU 底层实现(区别于标准 LRU)

Redis 没有采用全局双向链表(大量节点移动会阻塞主线程),采用轻量化近似 LRU:

  1. 存储标记:每个 redisObject 对象有 24 位 lru 字段,记录 key 最后一次访问时间戳;
  2. 淘汰逻辑:触发淘汰时,随机采样 N 个 key(配置 maxmemory-samples,默认 5),对比时间戳,淘汰采样中最久未访问的 key;
  3. 优势:无需维护全局链表,无频繁节点移动,性能损耗极低;
  4. 缺点:随机采样存在局部最优,无法保证全局真正最久未访问 key 被淘汰,采样数量越大精度越高。

3. Redis LFU 底层实现(4.0 版本新增,优化 LRU 缺陷)

LRU 缺陷:曾经高频、长期不访问的旧热点会长期占用内存(缓存污染);LFU 通过访问频率 + 时间衰减解决。

  1. 复用 redisObject 的 24 位 lru 字段拆分:
    • 高 16 位:记录上次频率衰减的分钟时间;
    • 低 8 位:访问频率计数器(Morris 概率计数器,节省内存);
  2. 计数器规则:每次访问有概率自增,不会无限上涨;
  3. 衰减机制:配置 lfu-decay-time,长时间未访问自动降低计数器,淘汰历史冷热点。

4. volatile-ttl 实现逻辑

仅筛选设置了过期时间的 key,读取 key 剩余 TTL 值,随机采样后淘汰剩余时间最小的 key,不关注访问行为。

四、延伸问题

  1. 过期删除和内存淘汰的执行顺序?

    写入数据内存超限 → 先尝试清理已过期 key;若内存仍不足,执行内存淘汰策略删除冷热 key。

  2. 为什么 Redis 不用标准 LRU 双向链表实现?

    全局双向链表每次访问都要移动节点,高并发海量 key 场景下大量链表操作阻塞主线程,大幅降低 Redis 吞吐。

  3. LRU 和 LFU 怎么选?

    • 访问冷热随时间快速切换(短期活动缓存):LRU;
    • 长期稳定热点、需要过滤一次性临时访问数据(资讯、推荐缓存):LFU。
  4. maxmemory-samples 参数作用?

    控制 LRU/LFU 淘汰时随机采样 key 的数量,默认 5;数值越大淘汰算法越精准,但 CPU 开销小幅上升。

posted @ 2026-07-07 09:23  lakewatcher  阅读(0)  评论(0)    收藏  举报