谈谈 Redis 的过期策略?
结论:Redis 用惰性删除 + 定期删除两种方式回收过期 key——访问时才判断是否过期,同时每 100 ms 抽样清理一部分。这套组合是"CPU 与内存的折中":既不为每个 key 起定时器,也不保证到期立刻释放。因此过期不等于内存立即释放,业务上必须把"逻辑过期"和"物理删除"分开理解。
一、三种可能的设计,为什么只选两种
| 策略 | 做法 | 问题 |
|---|---|---|
| 定时删除 | 每个 key 到期就起定时器删 | 海量 key 时定时器本身吃掉大量 CPU 与内存,Redis 不采用 |
| 惰性删除 | 访问 key 时检查,过期则删并返回空 | 不访问就永不删除,内存泄漏 |
| 定期删除 | 周期性随机抽样检查并删除 | 抽样不彻底,只能近似 |
| 组合(实际采用) | 惰性 + 定期 | 兼顾 CPU 与内存,代价是"删除有延迟" |
二、定期删除的抽样细节
每 100 ms 触发一次(hz 默认 10,即 serverCron 周期)
│
├─ 从每个 db 的 expires 字典中随机抽 20 个 key
├─ 删除其中已过期的
├─ 若过期比例 > 25%,再抽 20 个继续(同一轮)
└─ 单轮有时间上限(约 25% CPU 时间),超时就让出,下轮再来
这个"25% 阈值 + 时间上限"的设计保证:过期 key 密集时清理得快,但绝不会长时间霸占主线程。副作用是——大量 key 同时过期(集中过期)时,清理会反复触发,表现为延迟毛刺,这也是"TTL 要加随机抖动"的原因。
惰性删除路径(读命令执行时):
lookupKeyRead(key)
├─ 未过期 → 返回
├─ 已过期 → 删除该 key,返回 nil(同时向从库/AOF 传播 DEL)
└─ 从库不主动过期,等主库的 DEL 传播
三、观测与配置
# 看某个 key 的剩余存活时间(-1 永不过期,-2 不存在)
redis-cli TTL order:1001
redis-cli PTTL order:1001
# 统计过期 key 数量与内存
redis-cli INFO keyspace
redis-cli INFO memory | grep -E "used_memory_human|mem_fragmentation_ratio"
# 采样观察"已过期但仍在内存"的 key
redis-cli --scan --pattern 'session:*' --count 1000 | head -50 | \
xargs -I{} redis-cli TTL {}
# 定期删除的频率与力度
hz 10
dynamic-hz yes
# 内存到顶后的兜底:淘汰策略(过期 key 不是"被淘汰",是被清理)
maxmemory 4gb
maxmemory-policy allkeys-lru
四、几个容易答错的点
- 过期 key 还在占内存:
used_memory不会因为 TTL 到期立刻下降,要等惰性或定期删除命中;MEMORY USAGE仍能查到,TTL返回负数。 - 从库的过期:从库不主动删除过期 key,只等主库传播
DEL/UNLINK;主从延迟期间读到的是"逻辑过期但仍存在"的数据,关键业务要容忍或用主库读。 - 持久化文件里的过期 key:RDB 加载时主库会丢弃已过期 key,从库则保留(等主库 DEL);AOF 重写时也会剔除已过期 key。
EXPIRE覆盖SET:SET会清掉原有 TTL,改值又想保 TTL 要用KEEPTTL。PERSIST:把 key 变永久,容易埋下内存泄漏,使用前先确认业务真的需要。
五、常见追问
- 问:过期策略和淘汰策略有什么区别?答:过期策略处理"已到 TTL 的 key",目的是回收该删的数据;淘汰策略处理"内存到 maxmemory 时该挤掉谁",目的是控制内存上限。两者独立生效,可以同时存在。
- 问:为什么不用定时器精确删除?答:每个 key 一个定时器在百万级 key 下内存与调度成本都不可接受,且 Redis 单线程无法承受频繁的定时回调。
- 问:集中过期怎么避免?答:给 TTL 加随机抖动(如 30 分钟 ± 5 分钟)、把预热数据的过期时间打散、对超热 key 用逻辑过期(value 里带过期时间,异步重建)。
- 问:
hz调大能加快过期清理吗?答:能提高抽样频率,但也会增加 CPU 占用与延迟抖动;一般保持 10,配合dynamic-hz在空闲时自动提高。 - 问:内存没到 maxmemory,为什么还是看到内存不降?答:可能是过期 key 未被采样到、碎片率高(
mem_fragmentation_ratio > 1.5可考虑activedefrag),或有大量永不过期的 key。 - 问:大 key 过期会阻塞吗?答:会。释放大集合是同步的,可能造成百毫秒级卡顿;应使用
UNLINK(异步释放)或在业务低峰让大 key 过期。
小结
- 过期删除 = 惰性 + 定期抽样,是"够用就好"的近似方案,不承诺实时。
- 内存不降先区分三件事:没采样到、碎片、永不过期。
- 设计侧主动做两件事:TTL 加抖动、避免大 key 与集中过期。