连载中 9/16

过期删除与内存淘汰:key 到期了为什么不消失

2026-05-19 · 3267 阅读 · 0 评论 · 0 赞

内存 90% 告警,凶手是一堆「过期」的 key

凌晨收到内存水位 90% 的告警,我登上去一查,气笑了:几百万个会话 key,TTL 明明只有 2 小时,却堆了整整半天没释放。key 的到期时间早就过了,为什么不删?——如果你也有这个疑问,说明你已经摸到了 Redis 内存管理的第一层真相:到期和删除,是两回事

Redis 的主流程是单线程,如果每次 key 过期都立刻停下来全量清理,光是扫描就要把主线程卡成幻灯片。所以 Redis 的策略是:删除这件事,能拖就拖,能省则省——然后把内存的压力交给另一套机制兜底。这篇文章就把「过期删除」和「内存淘汰」这对经常被混淆的机制一次讲清。

到期 ≠ 立刻删除:两套拖延策略

Redis 删除过期 key 靠两条腿走路:

策略执行时机优点代价
定期删除默认每秒 10 次,随机抽一批带 TTL 的 key 检查主动回收,过期 key 不至于无限堆积占用 CPU,抽查模式不保证删干净
惰性删除key 被访问时才检查是否过期CPU 零开销,按需付费没人访问的过期 key 会常驻内存

定期删除是「抽样 + 循环」:每次随机抽 20 个带 TTL 的 key,过期的删掉;如果这批里过期比例超过四分之一,说明过期 key 太多,再抽一轮。听起来挺勤快,但它永远只抽样、不扫描全库——这就是开头那批「过期不删」的会话 key 幸存的第一个原因。惰性删除补第二刀:只有 key 被访问到,才会顺手发现它过期并删除。两套机制叠加的结果是:既没人抽查到、又没人访问的过期 key,就一直躺在内存里。这是拿「内存短暂浪费」换「CPU 不过载」的刻意权衡。

躺着不怕,怕的是越躺越多。当过期 key 堆积把内存顶到上限,第三套机制才正式登场——内存淘汰。

内存满了杀谁:八种淘汰策略

先纠正一个默认值陷阱:maxmemory 默认为 0,也就是不限内存——Redis 会一路吃到操作系统把它 OOM 杀掉为止。生产环境必须显式设置上限和淘汰策略:

# 给缓存实例设上限,并选好淘汰策略
maxmemory 6gb
maxmemory-policy allkeys-lru

八种策略按「淘汰谁、怎么挑」分成三组:

策略淘汰范围挑选规则
noeviction(默认)不淘汰内存满后写命令直接报错,读不受影响
volatile-lru设了 TTL 的 key淘汰最近最少使用
volatile-lfu设了 TTL 的 key淘汰最不经常使用
volatile-ttl设了 TTL 的 key优先淘汰剩余存活时间最短的
volatile-random设了 TTL 的 key随机淘汰
allkeys-lru全部 key淘汰最近最少使用
allkeys-lfu全部 key淘汰最不经常使用
allkeys-random全部 key随机淘汰

两个容易答错的面试题顺便清了:第一,LRU 是「最久没被用过」,LFU 是「用得最少」,前者看时间、后者看频率,访问有明显冷热长尾的数据用 LFU 更准。第二,Redis 的 LRU 不是教科书上那条双向链表,而是随机采样近似实现——每次随机摸几个 key 比一比,省下维护全量链表的内存。

策略怎么选:看实例里住着谁

  • 纯缓存实例:allkeys-lru 是标准答案;数据冷热差异大的场景换成 allkeys-lfu。
  • 混着不能丢的 key:比如分布式锁、全局配置——用 volatile 系。这类 key 不设 TTL,就永远不会被淘汰,牺牲的只有会过期的缓存。
  • 一个真实的坑:曾有团队把分布式锁存进 allkeys-lru 的实例,内存一紧,锁被「杀」了,互斥性直接失效。第 8 篇说过「锁是防线不是保命符」,这里补上后半句:锁要么单独实例,要么落在不会被淘汰的策略里

删除也分轻重:UNLINK 与 lazy-free

淘汰和过期都讲完了,还有最后一个坑:删除本身也会卡。DEL 一个百万元素的 hash,主线程要亲自把内存一块块释放,大 key 能卡住主线程几百毫秒。解法是 4.0 引入的异步删除:UNLINK 只把 key 从键空间摘除,真正的内存释放交给后台线程。Java 里对应 redisTemplate.unlink(key),用法和 DEL 一致。

顺手把过期和淘汰也一并异步化:

# redis.conf:过期、淘汰、隐式删除都交给后台线程
lazyfree-lazy-expire yes
lazyfree-lazy-eviction yes
lazyfree-lazy-server-del yes

再把日常编码的规矩立起来:

  • 所有写入显式带 TTL:拒绝裸 key,这既是第 3 篇讲过的容量纪律,也是让 volatile 系策略有活可干的前提。
  • maxmemory 必设:上限留出 20~30% 余量给复制缓冲区和碎片,别指望操作系统的 OOM 杀手手下留情。
  • 盯住 evicted_keys:这个指标开始增长,说明淘汰策略已经动真格在杀 key,缓存命中率和业务一致性都可能受波及。

总结一下这套内存管理体系:过期 key 靠定期加惰性「慢慢删」,内存满了靠 maxmemory 加淘汰策略「挑着杀」,删除大对象靠 UNLINK 和 lazy-free「异步删」。三层机制各管一段,谁也别替代谁。下一篇聊聊 Redis 的持久化:内存数据怎么落到磁盘?RDB 快照和 AOF 日志怎么选?重启之后的数据又该怎么救?下篇见。

咖啡凉了,记得趁热喝。

503

10 年全栈工程师 · 503咖啡馆主理人

#Redis#过期删除#内存淘汰#LRU#LFU

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞