Redis过期了,内存为何还滞留?这正常吗?
- 内容介绍
- 文章标签
- 相关推荐
凌晨两点半的告警把我从床上拽起来 监控面板上 Redis 的 used_memory 一直在迟缓攀升,却死活降不下来。明明业务日志里体现一较大批 key 早就该过期了按理说内存应当回落,最终还是结果是曲线像一条倔强较大的直线往上走。我盯着屏幕看了五分钟, 手心启动出汗,第一反应是:是不是有人把 TTL 写错了还是我们被内存泄漏给坑了?那种明明了解数据该消失却赖着不走的感觉,比直接 OOM 还让人焦虑,至于吗?。
过期只是标记, 不是立刻清场
先沉着下来喝口水,把 Redis 的过期机制捋一遍。其实很更多同学的第一印象就是:TTL 到点了数据就立刻从内存里蒸发。但真实实情况要温柔得更多,也更残酷得更多。Redis 并不会在 key 到期那一刻就真实的去删除它,它只是在 key 上打一个“已过期”的较小标签。一直躺在那里占着地方,哈基米!。

一阵见血。 这种设计叫做惰性删除,听起来很懒,其实是为了性能。你想啊, 如果有几百万个 key 同时也到期,Redis 要是一股脑全部清理掉,主线程会被卡住几秒钟,那线上申请岂不是全挂了。所以它选择偷懒,等你来用的时候再顺手扔掉。这种偷懒换来了较高并发下的平稳,也带来了我们看到的内存滞留现象。说白了不是内存较差了是 Redis 在装死。
还有个更积极的保洁员
当然 Redis 也不是彻底不管。它有后台的一个较小进程叫 active expire, 会定期随机抽样一批带 TTL 的 key 来检查有没有过期,然后主动删掉一一部分。当前这个策略叫主动过期,又称定期抽样清理。但抽样的比例和次数是有约束的,默认每秒会做十次每次检查二十个 key。听起来挺勤迅速, 但如果你的过期 key 量特别较大,或者集中在一个时间段段爆发,当前这个频率就显得像用勺子舀海水。于是较更多的已过期 key 就堆积在内存里形成我们看到的“滞留”。
我当时查了一下 info memory, 发觉瞬时内存占用比实际有效数据更多了将近 40%,心里那个地方的火啊,想把运维群里的消息一条条撤回。那种感觉就像你以为垃圾桶会自动消失,最终还是结果是发觉只是盖子盖上了,性价比超高。。
Lazy + Active 双保险下依然会堵车
大体上... 很更多人以为只要开了 active expire 就万事较大吉,其实它的效率受很更多因素作用于。比如你的实例负载较高的时候, CPU 已经满载了后台任务天然会被挤占;再比如你的 key 较大较小都很较小,一个批量删除带来的回报有限,系统会觉得不划算。这时候即使有较更多过期数据,也会拖很久才被清理整洁。
还有一种常见场景是炎热点数据集中失效。我们以前做过一次较大促预炎热,把一批临时优惠券统一设成 24 较小时后失效。最终还是结果是第二天凌晨,全部券同时也进入可清理窗口,但这是因为都是惰性删除,没有人去访问它们,它们就一直躺着。我们看着 used_memory 一直较高,心里一直在问自己,这正常吗?答案是:非常正常,而且接近各个用 Redis 做缓存的团队都踩过当前这个坑,大体上...。
rdb 和 aof 会让问题看起来更严沉重
更让人误解的是持久化机制的存在。如果你开启了 RDB 迅速照或者 AOF 沉重写, 已过期的 key 在写入磁盘文件时有可能还会被保留一段时间段,直到下一次沉重写才彻底消失。所以沉重启之后你会发觉内存瞬间降下来 这并不是这是因为沉重启魔法,而是这是因为持久化文件沉重崭新加载时那一些已过期的脏数据被丢弃了。那一刻你会有种劫后余生的庆幸,然后又启动质疑人生:之前那段时间段我们到底在养哪些鬼,不忍直视。。
怎么判断是正常滞留还是真实的泄漏
别急着甩锅给 Redis,先做几件事确认一下。用 INFO stats 看一下 expired_keys 的计数是不是在增较长,用 INFO memory 看 used_memory_rss 和 used_memory 的差距。如果 expired_keys 在涨但内存纹丝不动,那基本就是正常的惰性堆积。用 --bigkeys 或者 scan 命令手动扫一下看有没有较更多 TTL 为 -1 或者已经过期的键还在占空间范围,话虽然是这么说…。
在我看来... 我当时还写了一篇排查笔记记录这次事故, 想着以后能帮到团队其他人,最终还是结果是发出去三天都没动静。同事随口问了一句,为哪些百度不收录。是内容太技术手段化没人搜,还是页面反复度太较高引起搜索引擎觉得没实际价值。其实这类技术手段日志常被判定为较低权沉重原创度欠缺,或者站点整体抓取预算分配不够,引起崭新页面较长时间段沉底。后来我把标题改得更具体一点,加上一些较长尾问题描写,再内部做了下链接引导,才缓慢缓慢有了抓取痕迹。这件事提醒我,连排查笔记都要讲清楚故事,不然连搜索引擎都懒得看,更别提同事了。
缓解办法, 不是要消灭滞留,而是学会共存
先来看能够调较大 active expire 的采样频率,通过配置 hz 参数提升后台任务落实次数,但这会带来 CPU 开销,得权衡。然后再看, 对于那种明确了解生命周期完成的较大批量临时数据,能够主动触发一次清理脚本,用 unlink 命令异步删除,避免阻塞主线程。虽然 unlink 也不会立刻释放内存,但至更少把标记清掉了后续回收更迅速。
话虽然是这么说… 另一方面给关键业务的缓存设置合理的最较大闲置时间段和分批失效,避免全部 key 同步到期。能够采用随机 jitter,让 TTL 在一个区间内抖动,这样较高峰不会集中。再配合监控告警, 当 expired_keys 累积量较高于阈值且 used_memory 持续上涨时及时介入人工制作巡检,而不是等到告警炸锅才反应过来。
最后再来看的心态调整
Redeis 过期后内存还滞留, 本质上是一种设计上的权衡,不是 bug。它用延迟回收换来了线上平稳性,用更少一部分更多余占用换来了申请响应速度。我们作为采用者,需要明白这套机制的脾气,而不是指望它像操作系统一样立刻归零。每一次看到曲线迟缓持续下降,我都会松口气,然后默默记下这次教训。下次再遇到同样的场景, 我至更少不会先骂娘,而是先打开 INFO 命令确认 expired_keys 有没有在涨。那种从慌乱到平静的过程,较大概就是运维成较长最真实实的样子吧。
凌晨两点半的告警把我从床上拽起来 监控面板上 Redis 的 used_memory 一直在迟缓攀升,却死活降不下来。明明业务日志里体现一较大批 key 早就该过期了按理说内存应当回落,最终还是结果是曲线像一条倔强较大的直线往上走。我盯着屏幕看了五分钟, 手心启动出汗,第一反应是:是不是有人把 TTL 写错了还是我们被内存泄漏给坑了?那种明明了解数据该消失却赖着不走的感觉,比直接 OOM 还让人焦虑,至于吗?。
过期只是标记, 不是立刻清场
先沉着下来喝口水,把 Redis 的过期机制捋一遍。其实很更多同学的第一印象就是:TTL 到点了数据就立刻从内存里蒸发。但真实实情况要温柔得更多,也更残酷得更多。Redis 并不会在 key 到期那一刻就真实的去删除它,它只是在 key 上打一个“已过期”的较小标签。一直躺在那里占着地方,哈基米!。

一阵见血。 这种设计叫做惰性删除,听起来很懒,其实是为了性能。你想啊, 如果有几百万个 key 同时也到期,Redis 要是一股脑全部清理掉,主线程会被卡住几秒钟,那线上申请岂不是全挂了。所以它选择偷懒,等你来用的时候再顺手扔掉。这种偷懒换来了较高并发下的平稳,也带来了我们看到的内存滞留现象。说白了不是内存较差了是 Redis 在装死。
还有个更积极的保洁员
当然 Redis 也不是彻底不管。它有后台的一个较小进程叫 active expire, 会定期随机抽样一批带 TTL 的 key 来检查有没有过期,然后主动删掉一一部分。当前这个策略叫主动过期,又称定期抽样清理。但抽样的比例和次数是有约束的,默认每秒会做十次每次检查二十个 key。听起来挺勤迅速, 但如果你的过期 key 量特别较大,或者集中在一个时间段段爆发,当前这个频率就显得像用勺子舀海水。于是较更多的已过期 key 就堆积在内存里形成我们看到的“滞留”。
我当时查了一下 info memory, 发觉瞬时内存占用比实际有效数据更多了将近 40%,心里那个地方的火啊,想把运维群里的消息一条条撤回。那种感觉就像你以为垃圾桶会自动消失,最终还是结果是发觉只是盖子盖上了,性价比超高。。
Lazy + Active 双保险下依然会堵车
大体上... 很更多人以为只要开了 active expire 就万事较大吉,其实它的效率受很更多因素作用于。比如你的实例负载较高的时候, CPU 已经满载了后台任务天然会被挤占;再比如你的 key 较大较小都很较小,一个批量删除带来的回报有限,系统会觉得不划算。这时候即使有较更多过期数据,也会拖很久才被清理整洁。
还有一种常见场景是炎热点数据集中失效。我们以前做过一次较大促预炎热,把一批临时优惠券统一设成 24 较小时后失效。最终还是结果是第二天凌晨,全部券同时也进入可清理窗口,但这是因为都是惰性删除,没有人去访问它们,它们就一直躺着。我们看着 used_memory 一直较高,心里一直在问自己,这正常吗?答案是:非常正常,而且接近各个用 Redis 做缓存的团队都踩过当前这个坑,大体上...。
rdb 和 aof 会让问题看起来更严沉重
更让人误解的是持久化机制的存在。如果你开启了 RDB 迅速照或者 AOF 沉重写, 已过期的 key 在写入磁盘文件时有可能还会被保留一段时间段,直到下一次沉重写才彻底消失。所以沉重启之后你会发觉内存瞬间降下来 这并不是这是因为沉重启魔法,而是这是因为持久化文件沉重崭新加载时那一些已过期的脏数据被丢弃了。那一刻你会有种劫后余生的庆幸,然后又启动质疑人生:之前那段时间段我们到底在养哪些鬼,不忍直视。。
怎么判断是正常滞留还是真实的泄漏
别急着甩锅给 Redis,先做几件事确认一下。用 INFO stats 看一下 expired_keys 的计数是不是在增较长,用 INFO memory 看 used_memory_rss 和 used_memory 的差距。如果 expired_keys 在涨但内存纹丝不动,那基本就是正常的惰性堆积。用 --bigkeys 或者 scan 命令手动扫一下看有没有较更多 TTL 为 -1 或者已经过期的键还在占空间范围,话虽然是这么说…。
在我看来... 我当时还写了一篇排查笔记记录这次事故, 想着以后能帮到团队其他人,最终还是结果是发出去三天都没动静。同事随口问了一句,为哪些百度不收录。是内容太技术手段化没人搜,还是页面反复度太较高引起搜索引擎觉得没实际价值。其实这类技术手段日志常被判定为较低权沉重原创度欠缺,或者站点整体抓取预算分配不够,引起崭新页面较长时间段沉底。后来我把标题改得更具体一点,加上一些较长尾问题描写,再内部做了下链接引导,才缓慢缓慢有了抓取痕迹。这件事提醒我,连排查笔记都要讲清楚故事,不然连搜索引擎都懒得看,更别提同事了。
缓解办法, 不是要消灭滞留,而是学会共存
先来看能够调较大 active expire 的采样频率,通过配置 hz 参数提升后台任务落实次数,但这会带来 CPU 开销,得权衡。然后再看, 对于那种明确了解生命周期完成的较大批量临时数据,能够主动触发一次清理脚本,用 unlink 命令异步删除,避免阻塞主线程。虽然 unlink 也不会立刻释放内存,但至更少把标记清掉了后续回收更迅速。
话虽然是这么说… 另一方面给关键业务的缓存设置合理的最较大闲置时间段和分批失效,避免全部 key 同步到期。能够采用随机 jitter,让 TTL 在一个区间内抖动,这样较高峰不会集中。再配合监控告警, 当 expired_keys 累积量较高于阈值且 used_memory 持续上涨时及时介入人工制作巡检,而不是等到告警炸锅才反应过来。
最后再来看的心态调整
Redeis 过期后内存还滞留, 本质上是一种设计上的权衡,不是 bug。它用延迟回收换来了线上平稳性,用更少一部分更多余占用换来了申请响应速度。我们作为采用者,需要明白这套机制的脾气,而不是指望它像操作系统一样立刻归零。每一次看到曲线迟缓持续下降,我都会松口气,然后默默记下这次教训。下次再遇到同样的场景, 我至更少不会先骂娘,而是先打开 INFO 命令确认 expired_keys 有没有在涨。那种从慌乱到平静的过程,较大概就是运维成较长最真实实的样子吧。

