Skip to content

Redis 数据过期后的删除策略是什么? ​

  • 标签:后端、Redis

🏛 业务架构师 ​

Redis 的过期删除是惰性删除 + 定期删除的组合,两段式设计是成本权衡:

  • 惰性删除:访问 key 时检查过期则删。零后台成本,但没人访问的过期 key 永远占着内存;
  • 定期删除:默认每秒 10 次随机抽取设置了过期时间的 key 检查(每次默认抽 20 个,过期比例超 25% 则继续抽),在"清得干净"与"不能占住主线程"之间折中——单线程模型决定清理必须克制,所以过期 key 不会"准时"消失。

业务上的三个直接推论:

  1. 过期时间不是闹钟,是"最迟期限":expireat 10:00:00 的 key 可能在 10:00 后几秒~几分钟才真正删除。整点开抢、准点开奖类需求不能依赖 Redis 过期触发——要用分布式调度(xxl-job)主动触发 + Redis 只做状态存储。见过"用 key 过期当开抢信号"导致用户端时间错乱的案例;
  2. 内存水位要算"过期未删"的余量:批量写入带 TTL 的 key(验证码、session),到期但未被清理期间仍占内存,容量规划留 buffer;内存满时由淘汰策略(maxmemory-policy)接管——volatile-lru 只在设置了过期时间的 key 里淘汰,allkeys-lru 全量淘汰,不可丢的数据别放 Redis,这是策略选择的前置条件;
  3. 大量同刻过期是雪崩源:批量导入的缓存同一秒过期,请求集中穿透到 DB——TTL 要加随机抖动(基础值 + 0~300s 随机)。

一句话:把 Redis 过期当"软期限"来设计业务,准时性需求交给调度系统,这是理解这套机制的正确姿势。

🏗 系统架构师 ​

Redis 过期删除 = 惰性删除 + 定期删除组合,两者互补:

  • 惰性删除:访问 key 时(读写命令前)检查过期,过期则删除返回 nil。优点精确零成本(不访问不处理),缺点是冷 key 永远没人碰就一直占内存——"逻辑过期但物理存在";
  • 定期删除:默认每秒 10 次(hz=10)从设置了过期的 key 中随机抽 20 个检查,删除过期的;若过期比例超 25%,立即再抽一轮循环,单次有时间上限(默认 25% CPU 时间)防阻塞主线程。这是在"全量扫过期 key 太贵"与"只靠惰性太懒"之间的折中——抽样的成本可控性是单线程模型的必然选择。

删不干净的最后防线:内存淘汰策略。过期逻辑漏掉的 key 在内存达到 maxmemory 后按 maxmemory-policy 淘汰:noeviction(拒绝写,默认)、allkeys-lru、volatile-lru、allkeys-lfu(4.0+,按访问频率,热点缓存更准)、volatile-lfu、allkeys-random、volatile-random、volatile-ttl(快过期的先走)。

我的实践:缓存场景用 allkeys-lfu 或 allkeys-lru;有 TTL 语义的(session)用 volatile-*;设 maxmemory 硬顶 + 淘汰触发监控;hz 视过期 key 规模调到 10 以上;删除大 key 用 UNLINK 异步化,避免同步 del 阻塞事件循环。

💻 全栈开发专家 ​

组合策略:惰性删除 + 定期删除(单独用哪个都不行):

  • 惰性删除:访问 key 时检查过期时间,过期就地删除。零 CPU 预支,但没人访问的过期 key 会一直占内存。
  • 定期删除:默认每 100ms 随机抽取 20 个带 TTL 的 key 检查,删除其中过期的;如果本轮过期比例 > 25%,立刻再抽一轮(自适应:过期 key 多就多干活,少就快速收手),同时限制单次循环的 CPU 时间上限,防止阻塞主线程。

为什么不用定时删除(每个 key 到点精确删除):海量定时器拖垮 CPU。

但两者结合仍会有"漏网"的过期 key 占着内存,最后一道防线是内存淘汰(maxmemory-policy):内存达到 maxmemory 时触发。所以完整链路是:惰性 + 定期管删除效率,内存淘汰管容量兜底。

工程细节(我踩过的坑):

  1. 从库不主动删:过期判断由主库驱动(主库删除/过期后同步 DEL 给从库),从库读到已过期 key 时返回空但不删——存在短暂"从库能读到过期数据"的窗口;
  2. EXPIRE 的坑:SET key val 会清掉 TTL(覆盖语义),要保留过期时间得用 SET ... KEEPTTL,我修过一个"配置刷新后永不过期"的 bug 就是这个原因;
  3. 删除大 key 也会卡主线程,4.0+ 用 UNLINK 异步删。