作为高性能的键值对数据库,Redis被广泛应用于缓存、会话存储等场景。在使用Redis的过程中,我们经常会遇到key过期、内存占用过高的问题,而这背后就涉及到Redis的核心机制------过期删除策略与内存淘汰策略。今天,我们就从原理出发,全面整理这些关键技术,争取一文搞懂Redis是如何平衡CPU占用与内存利用率的。
01
什么是过期删除策略?
Redis支持为key设置过期时间,为了保证过期键值对能被及时清理,避免无效数据占用内存,就需要一套专门的机制来处理过期key,这套机制就是过期删除策略。
1. 如何判定key已过期?
当我们为key设置过期时间时,Redis会将该key与对应的过期时间存入一个**过期字典(expires dict)**中。这个字典的key是指向键对象的指针,value是一个long long类型的整数,代表key的过期时间点。

判定逻辑很简单,时间复杂度仅为O(1):
-
若key不在过期字典中,或过期字典中的过期时间大于当前时间,说明key正常;
-
否则,说明key已过期。
2. 三种核心过期删除策略详解
过期删除策略的核心矛盾的是"CPU占用"与"内存利用率"的平衡,不同策略给出了不同的解决方案,具体分为以下三种:
(1) 定时删除:时效性拉满,但CPU不友好
在设置key过期时间的同时,创建一个定时事件。当时间达到过期点时,由事件处理器自动执行key的删除操作。
定时删除优点是过期key能被立即删除,时效性最强,不会造成内存浪费;但缺点是如果存在大量过期key,定时事件会频繁触发删除操作,占用大量CPU资源,影响Redis的核心读写性能。
(2) 惰性删除:CPU友好,但可能浪费内存
不主动删除过期key,只有当客户端访问某个key时,才会检查该key是否过期。若过期则删除,否则返回正常键值对。
惰性删除优点是只在访问时才进行过期检查,几乎不占用额外CPU资源,对CPU最友好;但缺点是如果一个过期key长期不被访问,就会一直占用内存,造成内存空间的无效浪费(即"内存泄漏"风险)。
(3) 定期删除:折中方案,需精细配置
每隔一段时间,随机从数据库中抽取一定数量的key进行过期检查,并删除其中的过期key。
定期删除的优点是可以通过限制删除操作的执行时长和频率,平衡了CPU占用与内存浪费问题------既不会像定时删除那样频繁占用CPU,也不会像惰性删除那样任由过期key堆积;但缺点是时效性不如定时删除,性能不如惰性删除;且删除操作的时间间隔和抽取key的数量难以精准配置,配置不当可能导致CPU占用过高或内存清理不及时。
02
Redis的选择:惰性删除+定期删除
Redis没有选择单一的过期删除策略,而是采用"惰性删除+定期删除"的组合策略,在合理使用CPU时间和避免内存浪费之间取得最佳平衡。
1. Redis的惰性删除实现
当客户端访问或修改key时,Redis会先调用expireIfNeeded函数检查该key是否过期:
-
若过期:删除该key(4.0版本后可通过
lazyfree_lazy_expire参数配置同步删除或异步删除),返回null给客户端; -
若未过期:不做任何处理,返回正常键值对给客户端。
2. Redis的定期删除实现
定期删除的核心是"随机抽样+循环检查+时间上限",具体流程和配置如下:
-
时间间隔:默认每秒执行10次,可通过
redis.conf中的hz参数配置; -
抽样数量:代码中写死的常量,固定为20个key;
-
执行流程:① 从过期字典中随机抽取20个key;② 检查并删除其中的过期key;③ 若本轮过期key占比超过25%(即超过5个),则重复步骤①-②,直到占比低于25%;
-
安全机制:为避免循环过度导致线程卡死,设置了时间上限,默认单次定期删除循环不超过25ms。
03
特殊场景:主从模式下的过期键处理
当Redis运行在主从模式时,从库不会主动进行过期扫描,对过期key的处理完全被动:
-
即使从库中的key已过期,若有客户端访问,仍会返回key的原值,如同未过期;
-
过期key的删除由主库控制:主库检测到key过期时,会在AOF文件中添加一条
del指令,同步到所有从库,从库通过执行该指令删除过期key。
04
持久化场景:过期键在RDB/AOF中的状态
Redis的持久化文件有RDB(Redis Database)和AOF(Append Only File)两种格式,过期键在这两种文件中的呈现和处理逻辑不同,具体分为生成/写入阶段和加载/重写阶段。
1. RDB文件中的过期键
- 生成阶段:从内存持久化生成新RDB文件时,Redis会对key进行过期检查,过期key不会被写入新RDB文件,因此过期键不会影响RDB文件的生成。
- 加载阶段:加载RDB文件时,处理逻辑取决于Redis的运行模式:如果是主服务器,则加载时会检查文件中的key,过期key不会被载入数据库;如果是从服务器,无论key是否过期,都会被载入数据库。但由于主从同步时从库数据会被清空,因此过期键对从库加载RDB文件也几乎没有影响。
2. AOF文件中的过期键
- 写入阶段:以AOF模式持久化时,若过期key未被删除,AOF文件会保留该key的相关记录;当该key被删除后,Redis会向AOF文件追加一条del指令,显式标记该key的删除。
- 重写阶段:执行AOF重写时,Redis会对内存中的键值对进行检查,过期key不会被写入重写后的AOF文件,因此过期键不会影响AOF重写。
05
内存淘汰策略:当Redis内存超限时怎么办?
当Redis的运行内存超过设置的最大内存时,会触发内存淘汰策略,删除符合条件的key,保障Redis高效运行。在此之前,我们需要先了解如何设置Redis的最大运行内存。
1. 如何设置Redis最大运行内存?
可通过redis.conf中的maxmemory <bytes>参数设置最大运行内存。不同位数的操作系统,默认值不同:
-
64位操作系统:默认值为0,表示无内存限制。此时Redis不会检查可用内存,直到因内存不足崩溃;
-
32位操作系统:默认值为3GB。因为32位系统最大仅支持4GB内存,预留部分内存给系统运行,避免Redis因内存不足崩溃。
2. 八种内存淘汰策略分类详解
Redis共有八种内存淘汰策略,可分为"不进行数据淘汰"和"进行数据淘汰"两大类,其中"进行数据淘汰"又可细分为"仅淘汰设置过期时间的key"和"淘汰所有key"两个小类。
(1) 不进行数据淘汰:
noeviction(Redis3.0后默认策略):运行内存超限时,不淘汰任何数据。此时新数据写入会报错,但查询、删除等操作可正常执行。
(2) 进行数据淘汰:
① 仅在设置了过期时间的key中淘汰:
volatile-random:随机淘汰设置了过期时间的任意key;
-
volatile-ttl:优先淘汰过期时间更早的key; -
volatile-lru(Redis3.0前默认策略):淘汰设置了过期时间的key中最久未使用的key; -
volatile-lfu(Redis4.0新增):淘汰设置了过期时间的key中最少使用的key。
② 在所有key中淘汰:
-
allkeys-random:随机淘汰任意key; -
allkeys-lru:淘汰所有key中最久未使用的key; -
allkeys-lfu(Redis4.0新增):淘汰所有key中最少使用的key。
3. 查看与修改内存淘汰策略
实际使用中,可通过以下方式查看和修改内存淘汰策略:
-
查看策略:
config get maxmemory-policy; -
临时修改:
config set maxmemory-policy <策略名>(重启Redis后失效); -
永久修改:在
redis.conf中配置maxmemory-policy <策略名>(重启后仍有效)。
06
核心算法:LRU与LFU的实现与差异
在内存淘汰策略中,LRU和LFU是两种关键的淘汰算法,Redis对它们进行了优化实现,以适配自身的性能需求。
1. LRU算法:最近最少使用
LRU(Least Recently Used)即最近最少使用,核心思想是"淘汰最近最少被访问的数据"。传统LRU基于链表实现:
-
链表元素按访问顺序排列,最新访问的key移到表头;
-
淘汰时删除表尾元素(最久未使用)。
传统LRU的问题存在以下问题:
-
额外空间开销:需要维护链表管理所有缓存数据;
-
性能损耗:大量数据访问时,频繁移动链表元素会耗时,降低Redis性能。
为解决传统LRU的问题,Redis实现了近似LRU算法:
-
在Redis对象结构体中添加"最后访问时间"字段;
-
淘汰时采用随机采样:默认随机取5个key(可配置),淘汰其中最久未使用的key。
Redis近似LRU的实现,无需维护大链表,节省空间,也无需频繁移动元素,提升性能。但是缺点是无法解决"缓存污染"问题------若应用一次性读取大量仅使用一次的数据,这些数据会长期占用缓存,浪费内存。
2. LFU算法:最近最不常用
LFU(Least Frequently Used)即最近最不常用,核心思想是"淘汰访问频率最低的数据"。它通过记录数据的访问次数来判断优先级:访问次数越多,未来被访问的概率越高,越不容易被淘汰。LFU解决了LRU的缓存污染问题------一次性访问的大量数据,由于访问频率低,会被快速淘汰。
Redis利用对象头中24bits的lru字段实现LFU,该字段被分为两段:
-
高16bit(ldt):记录key的最后访问时间戳;
-
低8bit(logc):记录key的访问频次(非绝对访问次数,会随时间衰减)。
新key的logc初始值为5,每次访问key时,根据上次访问与当前的时间差,对logc进行衰减(时间差越大,衰减越多),然后再按概率增加logc的值(logc越大,增长越难)。此外,Redis提供两个配置项调整LFU的衰减和增长速度(在redis.conf中设置):
-
lfu-decay-time:单位为分钟,默认值1,控制logc的衰减速度(值越大,衰减越慢); -
lfu-log-factor:控制logc的增长速度(值越大,logc增长越慢)。
07
总结
Redis的过期删除与内存淘汰策略,本质上是对"CPU资源"和"内存资源"的平衡艺术:
-
通过"惰性删除+定期删除"组合,既避免了定时删除的CPU过载,也减少了惰性删除的内存浪费;
-
主从模式和持久化场景下的过期键处理,保障了数据一致性;
-
八种内存淘汰策略适配不同业务场景,而LRU/LFU算法的优化实现,进一步提升了缓存的合理性和性能。
掌握这些机制,能帮助我们在实际开发中更合理地配置Redis,避免过期key堆积、内存溢出等问题,让Redis发挥最佳性能。
你在使用Redis时,遇到过哪些关于过期key或内存的问题?欢迎在评论区留言讨论~