谈谈 Redis 的过期策略?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

谈谈 Redis 的过期策略?谈谈 Redis 的过期策略?

面试考察点

面试官提出这个问题,通常意在考察以下几个层面:

  1. 对缓存数据生命周期管理的理解:你是否清楚 Redis 作为缓存或数据库,需要有自动清理过期数据的能力。
  2. 对 Redis 核心机制的掌握深度:不仅仅是知道策略名称,更想知道你对其 底层实现原理、协同工作方式 的了解程度。
  3. 对系统资源与性能权衡的认知:如何设计过期策略才能在 内存利用率、CPU 消耗、数据一致性 之间取得平衡。
  4. 理论联系实际的能力:能否将策略与 内存淘汰策略 结合,并在实际开发中(如缓存穿透、雪崩)正确应用。

核心答案

Redis 的过期策略主要包含两个核心机制:惰性删除定期删除。它们是 Redis 用来清理过期 Key、回收内存的主要手段。此外,当这些策略无法及时释放足够内存时,会触发 内存淘汰策略 作为最后防线。

简单概括:

  • 惰性删除:访问 Key 时才检查并删除。
  • 定期删除:周期性抽样检查并删除。
  • 内存淘汰策略:内存不足时,按规则淘汰 Key(包括未过期的)。

深度解析

原理/机制

  1. 惰性删除 (Lazy Expiration)

    • 原理:当客户端尝试访问一个 Key 时,Redis 会先检查该 Key 是否设置了过期时间以及是否已过期。如果过期,则立即删除该 Key,并向客户端返回 nil。这是一种 被动、延迟 的清理方式。
    • 优点:对 CPU 友好,只有在必须时才进行删除操作,不会在无关的 Key 上消耗计算资源。
    • 缺点:对内存不友好。如果一个过期 Key 永远不再被访问,它将永远占用内存,造成 内存泄漏。惰性删除是保证 Redis 性能的第一道屏障。
  2. 定期删除 (Periodic Expiration)

    • 原理:为了弥补惰性删除的缺陷,Redis 会 周期性 地(默认每秒 10 次,即每 100ms)主动执行一次过期 Key 清理。这个过程是自适应的:
      • 每次从设置了过期时间的 Key 字典中,随机抽取一定数量(默认 20 个)的 Key。
      • 检查并删除其中已过期的 Key。
      • 如果本轮抽查中,过期 Key 的比例超过 25%,则 重复执行 抽查删除过程,直到过期 Key 比例降至 25% 以下,或者本次定期删除耗时过长(防止阻塞主线程)。
    • 优点:通过限制执行时间和频率,减少了对主线程的阻塞,同时一定程度上减少了“永远不访问的过期 Key”造成的内存浪费。
    • 缺点:它仍然是一种 抽样妥协 的方案。难以完全精确地删除所有过期 Key,在 Key 数量巨大时,仍可能有部分过期 Key 残留。
  3. 内存淘汰策略 (Eviction Policies)

    • 定位:这是过期策略的 补充和兜底 机制。当 Redis 内存使用达到 maxmemory 配置的阈值,且新写入数据时,如果惰性和定期删除没能及时释放空间,就会触发内存淘汰。
    • 8 种策略(Redis 7.x):
      • noeviction(默认):拒绝新写入,报错。适用于确需保留全部数据的场景。
      • allkeys-lru / volatile-lru:使用 LRU(最近最少使用)算法淘汰 Key。前者针对所有 Key,后者只针对设置了过期时间的 Key。
      • allkeys-lfu / volatile-lfu:使用 LFU(最不经常使用)算法淘汰 Key。LFU 比 LRU 更能精准识别热点数据。
      • allkeys-random / volatile-random:随机淘汰 Key。
      • volatile-ttl:优先淘汰 过期时间更短(TTL 更小) 的 Key。

    注意volatile-xxx 策略只在设置了过期时间的 Key 中淘汰,但如果这部分内存不足以满足新需求,依然会触发 noeviction 类似的行为(报错)。生产环境常用 allkeys-lruallkeys-lfu

协同工作原理

这两种删除策略与内存淘汰策略共同构成了 Redis 的内存管理闭环:

// 伪代码逻辑示意
public Object get(String key) {
    // 1. 惰性删除:访问时检查
    if (key.expired) {
        deleteKey(key);
        return null;
    }
    return data;
}

public void periodicTask() {
    // 2. 定期删除:后台循环任务
    do {
        sampledKeys = randomSample(expiredKeyDict, 20);
        deletedCount = deleteExpiredKeys(sampledKeys);
    } while (deletedCount / 20.0 > 0.25 && timeLimitNotExceeded());
}

public boolean writeData(String key, Object value) {
    // 3. 写入前检查内存,触发淘汰策略
    if (usedMemory >= maxMemory) {
        if (!executeEvictionPolicy()) { // 执行配置的淘汰策略(如LRU)
            if (policy == “noeviction”) {
                throw new RedisOOMError(); // 内存不足错误
            }
        }
    }
    // ... 执行写入
}
  1. 每次 读取操作 都伴随一次惰性删除检查。
  2. 定期任务 每秒运行多次,抽样清理,防止内存无限增长。
  3. 当内存触顶,写入操作 会触发配置的淘汰策略,移除一些 Key(可能是过期的,也可能是未过期的)来腾出空间。

最佳实践与常见误区

  • 最佳实践

    1. 根据业务形态选择淘汰策略:例如,缓存系统优先使用 allkeys-lru;需要保留热点长期数据、只缓存临时数据的场景,可使用 volatile-lru
    2. 合理设置过期时间:避免大量 Key 在同一时间点过期,防止定期删除压力陡增和缓存雪崩。可以添加随机偏移值,例如:expire = base_time + random(0, 300)
    3. 监控 expired_keys 指标:通过 info stats 命令查看,了解过期 Key 的清理速度是否正常。
  • 常见误区

    1. 误区一:“设置了过期时间,Key 到点就会立即被删除。” —— 错。删除依赖于惰性访问或定期扫描,存在延迟。
    2. 误区二:“volatile-lru 只淘汰过期的 Key。” —— 错。它只从 设置了过期时间的 Key 池子 里淘汰,但淘汰的依据是 LRU 算法,被淘汰的 Key 在淘汰那一刻可能并没过期。
    3. 误区三:“内存淘汰策略可以替代过期策略。” —— 错。过期策略是主动管理生命周期,淘汰策略是内存不足时的应急措施。二者目的不同,需配合使用。

总结

Redis 通过 惰性删除定期删除组合拳 来管理 Key 过期,在 CPU 和内存效率之间取得了平衡,并以 可配置的内存淘汰策略 作为内存不足时的最终保障;理解这套机制对于设计高性能、高可用的缓存架构至关重要。