先说结论

过期删除处理 TTL 到期的 Key,Redis 通过访问时惰性检查和后台主动采样删除组合控制成本;内存淘汰则在达到 maxmemory 后,根据 noeviction、LRU、LFU、random 或 TTL 等策略为新写入腾出空间。

过期 Key 不保证到期瞬间立刻物理删除,因此监控内存时要理解后台清理节奏。

淘汰策略

策略还分为 allkeys 和 volatile 范围:前者在所有 Key 中选择,后者只在设置 TTL 的 Key 中选择。纯缓存常用 allkeys-lru 或 allkeys-lfu;缓存与不可丢数据混用会让策略难以设计,最好拆分实例。

过期删除不是定时精确删除

Redis 不会为每个 Key 创建一个定时器。访问 Key 时会检查过期时间并惰性删除;后台周期性抽样带 TTL 的 Key,发现一定比例已过期就继续清理。这种组合避免为海量 Key 维护精确时间轮,但意味着过期 Key 可能短暂留在内存中。

因此业务不能把“TTL 到 0”理解为那一毫秒一定释放内存,也不能依赖过期作为毫秒级任务调度。需要精确延时执行时应使用延时队列或持久化任务系统。

maxmemory 触发后的行为

当 Redis 尝试分配内存且超过 maxmemory,才会按配置尝试淘汰或拒绝写入。不同策略意味着不同业务后果:

策略类型 含义 适合场景
noeviction 不淘汰,写命令报错 不能静默丢数据的实例
allkeys-lru/lfu 所有 Key 中近似淘汰 纯缓存
volatile-lru/lfu 只淘汰带 TTL 的 Key 明确只有缓存 Key 设 TTL 的混合场景
allkeys-random 随机淘汰 很少作为首选,成本低但命中率不可控
volatile-ttl 优先淘汰更快过期 Key TTL 与价值强相关时

如果选择 volatile 策略但大量缓存忘记设置 TTL,候选集合可能很小,写入仍会失败。策略配置必须与写入规范一起评审。

LRU 和 LFU 的现实含义

Redis 使用近似采样算法,不维护全量严格的访问排序。LRU 偏好最近访问,LFU 偏好长期频繁访问,LFU 还会对计数进行衰减以避免历史热点永久霸占缓存。采样大小、访问分布和内存压力都会影响实际淘汰结果。

选型前应看业务数据的访问曲线:短期突发热点可能更适合 LRU,长期稳定热门内容可能受益于 LFU;最可靠的依据是线上命中率、淘汰量和回源成本,而非算法名称。

TTL 设计方法

TTL 应反映数据的新鲜度、重建成本、写入失效机制和容量目标。可以用“基础 TTL + 随机抖动”打散到期,但不要为了抖动让低价值数据长期占内存。对强一致写后读,TTL 不能替代主动失效;对逻辑过期数据,物理 TTL 应比最大允许陈旧时间更长一些作为安全清理。

商品详情:30 分钟 + 0~5 分钟抖动
登录验证码:5 分钟且单次使用后立即删除
分布式锁:仅覆盖临界区预期时长,必须有续期/超时策略

监控与容量预案

持续观察 used_memory、maxmemory、mem_fragmentation_ratio、evicted_keys、expired_keys、keyspace_hits/misses、命令延迟和回源 QPS。淘汰开始持续增长并不只是 Redis 问题,往往说明容量预算、TTL 设置或流量模型发生变化;应提前扩容、清理低价值数据或调整应用缓存策略,而不是等写入大量报错。

TTL 设计

TTL 应来自业务新鲜度要求,并增加随机抖动避免集中到期。更新 TTL 时注意覆盖写是否保留或清除原过期时间;使用命令时应明确版本语义并测试。

容易踩坑的地方

Redis 的 LRU/LFU 通常是近似算法,不会维护全量精确排序。设置淘汰策略也不是容量治理的替代品,频繁驱逐会造成命中率下降和数据库压力。

常见问题

追问:怎么判断正在发生严重淘汰?

观察 evicted_keys 增速、命中率、内存使用、写入错误和下游回源量,并关联实例 maxmemory 与业务流量变化。

追问:EXPIRE 会覆盖已有 TTL 吗?

通常会按命令语义更新过期时间,某些带条件选项的版本可限制仅在无 TTL、已有 TTL 等条件下生效。关键业务应明确使用的命令版本并写测试。

追问:删除一个大 Key 为什么会卡顿?

某些复杂类型释放大量元素需要较长时间,可能阻塞事件循环。可使用异步释放、渐进删除或按业务维度拆小 Key。

追问:缓存实例可以存永久 Key 吗?

可以,但在 allkeys 淘汰下它仍可能被淘汰;在 volatile 策略下它可能挤占内存却不成为候选。永久业务状态和缓存通常应分实例或至少分清可靠性边界。