尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Redis过期键删除机制详解:惰性删除、定期删除与内存淘汰

发布时间:2026/9/26 13:22:24

资讯中心
01
ARTICLE

Redis过期键删除机制详解:惰性删除、定期删除与内存淘汰

Redis过期键删除机制详解:惰性删除、定期删除与内存淘汰
1. 先从内存不降讲起过期键到底什么时候被删做Redis运维和开发的人十有八九都遇到过这么个怪现象明明给业务key都设了TTL过期时间ttl key查出来也确实是正数但用info memory一看内存占用就是降不下来。甚至有些key的TTL已经变成-2了也就是已经过期了内存里却还占着位置。这不是Redis出bug了而是过期键删除机制本身就不是到点就删。Redis官方文档把过期键删除分成两个核心策略惰性删除lazy expiration和定期删除active expiration另外还有一个容易被混为一谈的兜底策略内存淘汰eviction。三者的关系简单说就是惰性删除被访问时发现过期顺手删除定期删除后台定时任务主动抽查清理一部分过期key内存淘汰内存达到maxmemory上限时按策略强制移除key。这三者不是三选一而是同时生效、互相补位。我见过不少刚入门的同学以为设了Redis的TTL就万事大吉也见过有经验的工程师把expired_keys和evicted_keys两个统计指标搞混最后排查方向完全跑偏。这篇文章就把这三层机制扒开讲透顺便带一下持久化和主从复制场景下过期键的特殊行为这些都是面试里高频追问、实际踩坑时又很难快速定位的点。2. 为什么Redis不选择定时删除一杆子打死全量扫描先回答一个最朴素的问题既然有TTL为什么不能在过期时间点立刻把key删掉这是很多人第一反应想到的方案——给每个key挂一个毫秒级定时器到点触发删除。理论上没问题实际上不可行原因有两条。2.1 单线程事件循环扛不住大规模定时器Redis的核心是单线程事件循环所有命令都在一个线程里串行执行。如果为每个过期key维护一个精确到毫秒的定时器就意味着又拍epoll之外还要维护一套高精度时钟调度。几百万个key几百万个定时器光是管理这些定时器的内存和CPU开销就是巨大的负担。更麻烦的是一旦某个定时器到点线程必须立刻切换到删除任务上这会打断正在处理的正常命令造成明显的请求延迟抖动。2.2 全量扫描的代价是O(N)而且很可能大部分key没过期也有人说那不用定时器每分钟把所有key扫一遍过期的删掉不就行了。这在key数量少、删除频率低的场景下可以但Redis是内存数据库线上实例几百万key是常态全量扫一次做了大量无用功——大多数key根本没到期。更关键的是扫描全过程会阻塞主线程而Redis号称单线程性能神器你让它每秒或每十秒全量扫一次性能直接崩。所以Redis选择了一条折中路线平时主要靠惰性删除兜底让访问行为去触发删除同时用一个频率不高、耗时可控的定期任务去主动清理那些一直没被访问的过期key。这套组合的核心理念是**用最小的CPU代价换取内存的可回收性**——内存和CPU本来就是权衡Redis把主动权交给了工作负载。3. 惰性删除每条命令入口的体检关卡先看看Redis源码里最基础的过期检查函数expireIfNeeded位于db.c。所有命令在真正操作一个key之前几乎都要经过一层检查这个key过期没有如果过期了直接当它不存在然后顺手把这个key从内存里删掉。int expireIfNeeded(redisDb *db, robj *key, int flags) { // 如果key没有关联过期时间直接判定不过期 if (!keyIsExpired(db, key)) return 0; // 主库删除过期key并传播DEL命令到AOF和从库 ... }3.1 命中过期key的完整处理链路比如你执行GET user:10086这个key设置了60秒过期第61秒来访问。实际流程是这样的Redis在dict.c的哈希表里按key名查找到entry查到之后取出这个key的过期时间字段做比对发现当前时间大于过期时间判定为过期从主字典里删除这个entry释放内存同时往AOF缓冲区和所有从库传播一条DEL命令保证持久化和副本侧一致性对调用方返回这个key不存在。这一步做完客户端拿到的就是nil或空结果同时Redis内存里也真正少了这块占用。3.2 为什么访问不到不代表不存在这里有个特别容易踩坑的点在逻辑上过期key对外表现为不存在但在物理内存上它可能仍然存在。因为惰性删除只在key被访问的时候才触发如果一个key过期后一直没人访问它就永远躺在内存里不占CPU、不占逻辑空间但占物理内存。这就是文章开头TTL已经-2但内存不降的第一层原因。另一个冷门细节EXISTS、TTL这类命令也会触发过期检查吗实际上Redis对这两个命令的处理是特殊的EXISTS查一个已过期的key返回0TTL查一个已过期的key返回-2但它们不会触发物理删除。只有真正需要读取key内容的命令比如GET、SET覆盖、LPUSH、HGET等才会走完整的expireIfNeeded物理删除逻辑。这个设计也是出于性能考虑——没必要为了一个简单的存在性查询付出删除的代价。3.3 惰性删除的致命短板惰性删除最大的问题是不可控过期key删除时机完全取决于业务访问模式。如果一个key过期后没有任何客户端再碰它那它就一直在内存里直到定期删除或内存淘汰来收拾残局。你设了24小时过期的验证码用户第二天没来登录这个key就可能一直躺着。所以仅靠惰性删除Redis的设计者很清楚内存会变成垃圾场这才有了下面的定期删除机制。4. 定期删除serverCron驱动的抽样清理工定期删除由Redis的后台周期任务serverCron触发默认每100毫秒运行一次。核心函数是activeExpireCycle它不是全量扫描而是按db逐个抽样清理目标是用可控的CPU耗时把过期key占比压到合理水位。4.1 一次清理循环里到底干了什么我按源码逻辑简化一下activeExpireCycle的主流程从0号db开始逐个db处理单次轮询最多处理16个db防止一个周期耗时过长针对当前db从过期key哈希表的随机位置开始最多取出20个keyACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP宏默认20检查是否过期如果这20个key里过期比例超过25%说明这个db里过期key浓度过高就继续取下一批20个key再次检查如果20个key里过期比例低于25%说明当前db状况健康换下一个db整个过程中每隔一段会有timelimit时间上限默认1毫秒检查一旦超过时限无论扫没扫完立刻让出CPU。关键参数就是这个25%和1毫秒。25%的判断标准保证了过期货很多时能持续加大清理力度1毫秒的时间上限保证了无论如何不能影响正常命令处理。4.2 为什么抽样20个就能起到清理作用这是很多人的疑问一个db里可能有几十万过期key一次只扫20个猴年马月才扫得完其实关键在于循环机制每秒大约10次serverCron调用每次都会从哈希表的某个随机游标处开始取样。只要过期key占比高触发的循环次数就多一轮一轮迭代下去最终会覆盖到整个哈希表的所有槽位。而哈希表本身就是O(1)访问抽样是无序的不存在头尾遗漏的问题。加上过期比例超过25%继续扫这个反馈条件清理工作会自适应地加速或降速。4.3 redis.conf里能调的旋钮Redis在redis.conf里提供了几个跟定期删除相关的可调参数版本不同名称略有差异配置项默认值作用hz10serverCron每秒执行次数默认10次即每100ms一次。调到100会提高清理频率但CPU开销也上升active-expire-effort1Redis 5.0后引入取值范围1~10数值越大每次定期清理的循环次数越多CPU耗时越长我一般不推荐轻易把hz调得过高——这会影响很多依赖serverCron的周期性任务比如超时检测、复制心跳。如果确实遇到过期key堆积优先调active-expire-effort它专门控制过期清理的强度影响面更小。5. 内存淘汰策略maxmemory之下的一锤子买卖就算惰性删除和定期删除都在运行也仍然存在一个缺口如果业务产生的key写入速度远大于过期key清理速度内存就会持续上涨直到触发maxmemory上限。这时候Redis不会傻到继续写入把内存撑爆而是启动内存淘汰策略eviction强行移除现有key给新key腾地方。5.1 八种淘汰策略傻傻分不清很多线上事故的根源就是maxmemory-policy配错了。先把全貌列出来策略含义代表场景noeviction内存满后不淘汰写入直接报OOM错误需要绝对不丢数据的队列或存储allkeys-lru从所有key里淘汰最久未访问的纯缓存不区分业务数据allkeys-lfu从所有key里淘汰访问频率最低的缓存访问热度差异极大allkeys-random从所有key里随机淘汰访问完全无规律volatile-lru从设置了TTL的key里淘汰最久未访问的只淘汰可过期的业务keyvolatile-lfu从设置了TTL的key里淘汰访问频率最低的同上看重访问频率volatile-random从设置了TTL的key里随机淘汰只淘汰可过期的业务keyvolatile-ttl从设置了TTL的key里淘汰剩余TTL最短的想让快过期的key先走关键分界线就是allkeys和volatileallkeys系列不区分key有没有TTL内存不够时谁的淘汰代价低就淘汰谁volatile系列只在设置了过期时间的key里选永远不碰那些没有TTL的持久key。5.2 过期键删除和内存淘汰不是一回事说个我见过很多次的混淆有人用INFO stats去看evicted_keys飙升以为是过期键没有被清理实际上evicted_keys统计的是因为内存上限被强制淘汰的key数量跟过期键删除是两个完全不同的动作。过期键删除在逻辑上是你自己设了TTL之后的自然死亡内存淘汰则是系统为了存新数据而做的强行牺牲。判断出问题的是哪一类看两个指标就够了# INFO stats 片段 expired_keys:12345 # 因为过期而被删除的累计数量 evicted_keys:678 # 因为内存上限被淘汰的累计数量如果expired_keys增长缓慢而evicted_keys在飙升说明过期键清理机制没失效而是业务写入速度超过了清理速度该看的是maxmemory-policy和业务TTL设置是否合理。5.3 一个血泪教训volatile-lru配错导致缓存全部命中失败我有一次排查线上缓存命中率暴跌INFO stats显示evicted_keys每分钟涨几千keyspace_hits从95%掉到40%。查下来发现maxmemory-policy配成了volatile-lru而业务有个核心配置表缓存根本没设TTL——内存紧张时volatile-lru只会淘汰那些设了TTL的缓存不设TTL的配置缓存反而留了下来。结果是一大堆设置了TTL的短缓存全被赶出去命中率自然崩了。纯缓存场景的正确姿势是**allkeys-lru**因为缓存里根本没有必须保留的数据哪个最久没访问就淘汰哪个TTL反而不重要。而如果业务里混着必须持久化的数据和可以过期的临时数据才需要volatile-lru——但它有一个副作用不设TTL的key永远不会被淘汰内存满时可能引发写阻塞。6. 持久化与主从复制场景过期键的另一个版本上面聊的删除策略主要发生在内存运行态但Redis还有RDB持久化、AOF持久化和主从复制几套机制过期键在这些场景下的行为很容易被忽略面试和实战里都是重灾区。6.1 RDB文件写入时过滤加载时不拦生成RDB快照时Redis会以当前内存数据为准进行序列化已经过期的key不会写入RDB文件。这个过滤是在rdb.c的遍历过程中通过expireIfNeeded判断的所以生成的快照里天然没有过期垃圾。但在加载RDB文件时Redis反而会无条件先把所有key载入内存然后再根据过期时间逐个标记。为什么不在加载时就把过期key过滤掉因为RDB文件可能是从旧版本生成的加载方需要先恢复完整数据集再由主线程的惰性/定期删除去负责清理。加载的一瞬间临时内存占用会有短时抬升这是正常现象。6.2 AOF删除命令也会进入日志AOF模式下每一次惰性删除和定期删除删掉过期key时都会向AOF缓冲区的日志流里追加一条DEL命令。这样做的目的是保证AOF文件重放之后数据能和主库当前状态完全一致——你删了不记下来重放时那个key就又回来了。AOF重写BGREWRITEAOF时更干脆重写过程遍历当前内存数据已经过期的key直接跳过不写入重写后的AOF文件。所以一个常见现象是AOF文件重写之后体积突然变小除了业务本身删数据以外很大一部分就是过期垃圾被滤掉了。6.3 主从复制主库删除从库等命令主从架构下过期键删除有个著名的不对称设计主库碰到一个过期key会把删除动作同步给从库通过传播DEL命令从库本身即使收到读取过期key的请求也不会主动物理删除key而是直接返回nil从库默认replica-read-only yes写不进来自然也无从触发。这个设计的初衷是避免主从不一致如果主从各自独立删除过期key删除时机受各自访问负载影响可能同一个key在主库删了、在从库还活着造成客户端读到不同数据。让从库的删除动作始终追随着主库的DEL传播一致性才能保证。但这里也有坑如果主从之间网络中断主库的DEL命令迟迟传不到从库从库内存里这些过期key会一直占着地方。恢复同步后从库也会因为全量或增量同步过程中收到删除命令而慢慢清掉。线上如果发现从库内存比主库高出一截先检查是不是这个原因。6.4 哨兵和集群模式下要注意的额外事项在Redis Cluster和Sentinel架构里key分布在多个主节点上每个主节点各自独立执行惰性删除和定期删除——机制和单机完全一样但日常运维要把每个节点的过期键监控数据都拉出来看不能只盯一个主节点。另外从库提升为主库failover之后那些之前在从库里残留、没有被主库DEL命令光顾过的过期key会在新主库被后续访问时通过惰性删除补掉但存量内存不会瞬间释放要给定期删除一些时间。7. 观察与调优线上如何判断过期键清理是否正常最后给一套可以直接抄的实测排查链路。遇到Redis内存只涨不降或删除不生效的问题按下面的顺序查。7.1 先看三组info指标# 基本信息 used_memory_human:1.5G maxmemory_human:2.0G # db0整体key数量 db0:keys890123,expires450000,avg_ttl86400 # 删除统计 expired_keys:45231 evicted_keys:12逐行判断思路expires占比高不高如果450万key里有400万都设了TTL说明业务本身大量依赖过期机制expired_keys是否持续增长如果过了一天还纹丝不动要么最近没有key到期要么定期删除可能没跑到evicted_keys是否飙升前面说过这是内存淘汰的账跟过期键清理是两码事平均TTLavg_ttl如果很短说明大量key生命周期极短这会导致过期清理压力极大。7.2 用redis-cli实时盯变化redis-cli -h yourhost -p 6379 --stat -i 5这条命令每5秒打印一批keyspace_hits、keyspace_misses、used_memory、expired_keys、evicted_keys等统计字段。观察几分钟后如果used_memory总体平稳或缓慢下降说明惰性删除和定期删除在工作如果used_memory单边上涨接近maxmemory就要警惕了。7.3 定位过期键堆积是否属于Redis 4.0之前的旧版病Redis 4.0之前activeExpireCycle有一些被人诟病的问题比如一次循环内遍历的db数量限制不足、过期key分布不均时可能某个db长期扫不到、删除瞬间CPU升高等。4.0引入了active-expire-effort和更细致的循环时间切片7.0之后又进一步优化了抽样游标的推进方式。如果你还在用3.x版本线上出现内存堆积可以理解但建议尽快升级——大规模过期key场景下旧版定期删除的抽样逻辑确实不够稳。7.4 针对热点key集中过期的叹气通道问题经常有一段业务数据设置了统一的TTL比如缓存全部在零点过期那么零点整会有成批key同时到期。此时expired_keys瞬间暴增、主库CPU出现尖峰。规避手段有三个给TTL加一个随机抖动比如TTL 基础TTL random(0, 3600)把过期时间摊平错峰设置不同业务线使用不同的过期基点如果已经是集中过期发生后的CPU毛刺手动执行一次redis-cli --scan --pattern batch*让惰性删除提前触发热点key把压力分散到之前的时间段。这三个手段里最推荐第一个因为成本最低而且对业务透明。随机抖动摊平之后定期删除每次抽样遇到的过期比例会低很多CPU自然平缓。7.5 关于内存碎片的补充used_memory降了不代表RSS降了还有一个经常被和过期键删除混在一起的坑used_memory下降了但info memory里的used_memory_rss实际占用物理内存没降。因为Redis使用jemalloc等内存分配器删除key释放的内存未必立刻还给操作系统可能留在内存池里供后续分配复用。这在Linux上表现为进程RSS高居不下容易让人误以为过期键没删干净。判断指标是mem_fragmentation_ratio如果这个比值大于1.5甚至2说明碎片较多可以在低峰期执行memory purge或重启实例整理碎片但那已经是内存分配器层面的问题了和过期删除策略无关。8. 实战中我认为最值得记住的三条原则想用一句话总结Redis过期键删除策略的话我会说惰性删除保证正确性定期删除保证内存不过度膨胀内存淘汰保证可用性三者缺一不可。但真正在工作中帮我少踩坑的是下面三条更具体的体会。第一永远不要把过期键清理当成立即释放内存的手段。它是一个渐进式的后台过程尤其在大量短TTL key的场景下释放速度一定赶不上写入速度。评估容量时给内存留30%左右的buffer比事后调淘汰策略省心得多。第二监控指标要分开看。expired_keys涨得猛说明业务TTL密集evicted_keys涨得猛说明容量规划有问题mem_fragmentation_ratio高说明碎片严重。这三个都归结到内存降不下来时处理方式完全不同——不看指标直接加内存是最偷懒也最容易掩盖问题的做法。第三给所有设置了TTL的key都设计一个可预期的淘汰水位。也就是即使所有过期键同时失效、淘汰策略同时触发系统也要能扛住。Redis在key多、TTL短、淘汰策略不当时会出现写入一条就淘汰一条的颠簸状态此时evicted_keys和写入量几乎同步增长CPU和延迟双双恶化。遇到这种症状第一件事不是调maxmemory-policy而是调业务方的TTL设计——把大部分key的过期时间拉长、加抖动、减少短生命周期key的占比往往比任何Redis层面的参数都见效快。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。