资讯详情

Redis内存淘汰 vs 过期删除:机制差异、8种策略与调优实践

📅 2026/10/16 4:18:20 | 华诺云谱 👁 阅读
Redis内存淘汰 vs 过期删除:机制差异、8种策略与调优实践
1. 项目概述两个最容易混淆的Redis机制做Redis开发的人内存淘汰策略和过期删除策略这两个概念我见得太多次被搞混了包括我自己刚入行的时候也吃过亏。有一次生产环境的Redis实例内存直接被打满我第一反应以为是有大量key过期没被清理后来排查下来才发现是淘汰策略配置的问题。这两个机制虽然都和“清理数据”有关但触发时机、作用对象、设计目的完全不同今天这篇就系统地把它们掰开揉碎讲清楚。简单来说过期删除策略负责的是“该删的删掉”内存淘汰策略负责的是“装不下了得腾地方”。过期删除管的是那些设置了TTL生存时间的key到了时间就该消失而内存淘汰是在Redis内存达到上限时按照预设的规则选一批key干掉。前者是主动维护数据生命周期后者是保命手段。两者互相配合共同保障Redis在有限内存下既不丢业务数据也不至于OOM崩溃。这篇文章适合正在系统学习Redis原理的开发者、准备面试的技术同学以及已经在生产环境使用Redis但遇到过内存告警、缓存清空等问题的运维和开发人员。我会先从两者的本质区别讲起再把过期删除的两种实现方式、Redis内置的8种淘汰策略逐一展开最后给出实操配置和监控手段以及我实际踩过的几个坑。看完之后你至少能做到遇到内存问题能快速判断是哪一层机制在起作用并且能根据自己的业务场景选出正确的淘汰策略。2. 过期删除策略Redis主动维护key生命周期的三种思路2.1 惰性删除访问时检查不查不删惰性删除的核心逻辑非常朴素——当客户端请求一个key时Redis会先检查这个key是否设置了过期时间如果已经过期就立即删除然后返回nil相当于这个key对客户端来说不存在了。这里有个关键点很多人没意识到如果一个key过期了但一直没有人访问它那么它就会一直躺在内存里不会立刻被物理删除。也就是说内存里可能堆积大量“逻辑上已过期但物理上仍存在”的key。惰性删除的设计初衷是为了节省CPU开销只有在访问时才去做过期判断避免为了清理一个没人用的key浪费主线程的算力。但单纯靠惰性删除有明显隐患。比如有个业务往Redis里写了一批有效期5分钟的验证码之后客户端一直没来读取这些key就会一直占用内存。如果这种key量大内存会被无效数据慢慢蚕食。正因为惰性删除的滞后性Redis还需要另一种机制来兜底——定期删除。2.2 定期删除周期抽样扫描平衡CPU和内存定期删除是Redis在后台周期性执行的一种清理机制。它的工作方式不是遍历所有key而是采用抽样扫描的策略。Redis每隔一段时间默认100ms会随机抽取一批设置了过期时间的key检查其中有多少已经过期按比例决定是否继续多轮清理。这个“按比例”的设计很巧妙。如果抽样的key中过期key占比超过一定阈值说明过期key较多Redis会继续执行清理直到比例降下来如果占比低就提前结束本轮扫描。这样做的好处是把清理频率动态化过期key多的时候清理得快过期key少的时候不浪费CPU。定期删除还有一个细节容易被忽略——它并不是在独立的线程里跑的而是在Redis的主事件循环中执行的。这意味着清理操作本身也会占用主线程的时间片。所以定期删除的频率和抽样数量不能设置得太激进否则会拖慢正常的读写请求。从实际观察来看默认参数在大多数场景下是够用的不太需要手动调整。2.3 为什么Redis没有采用“定时删除”可能有人会问最简单的方案不是每个key到时间就立刻删掉吗这就是定时删除的思路。Redis没有这么做核心原因是它在高并发场景下不可行。如果每个key都绑定一个定时器那么当大量key集中过期时CPU会被突如其来的定时器回调打满造成明显的请求延迟抖动。这和“惰性定期”结合的设计思路形成了鲜明对比惰性删除把开销分散到每次访问中定期删除把开销控制在周期性的小批量清理中两种方式都不会出现CPU尖峰。所以Redis最终选择了“惰性删除为主、定期删除为辅”的组合方案。这个设计在工程上非常经典——既不过度消耗CPU又能防止过期key无限堆积。当然组合方案也有一个副作用如果大量key在同一时间点过期定期删除未必能及时全部清理掉内存可能瞬间被“逻辑垃圾”占据。后面的内存淘汰策略其实就是在这个缺口之上加的最后一道防线。3. 内存淘汰策略内存不够时Redis如何抉择3.1 触发条件与核心前提maxmemory内存淘汰不是随时发生的它的触发前提是开启了内存上限配置。Redis通过maxmemory参数控制实例的最大内存占用默认值为0表示不限制内存。一旦设定了上限当实际使用内存达到这个阈值时新写入请求就会触发淘汰逻辑。这里必须强调一个容易犯错的点Redis达到maxmemory后写操作会报错在noeviction策略下或者触发淘汰但读操作不受影响。很多新手以为内存满了Redis就完全不可用了实际并不对。另外maxmemory设置的值应该留出系统余量不要把物理内存的100%都分给Redis否则操作系统本身和其他进程的内存压力会导致实例被系统OOM Killer干掉。触发淘汰时Redis选择的时机是“每次执行命令之前”。如果当前内存已经超过maxmemory写入命令会先尝试淘汰一批key腾出空间后再执行。这意味着淘汰过程是一次或多次的循环操作直到内存降到上限以下。3.2 8种淘汰策略逐项拆解Redis一共内置了8种淘汰策略可以按照作用范围分成两大类只针对“设置了过期时间的key”进行操作以及针对“所有key”进行操作。下面这个表格是我整理的速查版本。策略名称作用范围淘汰依据适用场景noeviction全部key不淘汰写失败对数据完整性要求极高的场景allkeys-lru全部key最近最少使用缓存场景希望保留热点数据volatile-lru设置了TTL的key最近最少使用只淘汰可过期的key保护长期keyallkeys-random全部key随机淘汰访问分布均匀无显著热点volatile-random设置了TTL的key随机淘汰过期key集合较大随机性可接受volatile-ttl设置了TTL的key剩余存活时间最短优先优先淘汰即将过期的keyallkeys-lfu全部key最不经常使用访问频率差异明显的业务volatile-lfu设置了TTL的key最不经常使用频率淘汰且只淘汰可过期key逐个说一下关键差异。noeviction是Redis默认的淘汰策略在内存达到上限后不进行任何淘汰直接向客户端返回错误。这个策略在缓存场景几乎不能用因为一旦内存打满整个写入就停了业务会受到实质影响但它保证了数据不会因为淘汰而“不明不白消失”适合那些把Redis当数据库用、绝不能隐式丢数据的场景。allkeys-lru可能是生产环境中使用率最高的策略。它从所有key中挑选最久没被访问的key进行淘汰。比如一个热门商品详情缓存用户刷新的频率高它的“最近访问时间”就不断被刷新不容易被选中而那些写进去后长期没人读的key很快就会被清掉。这个策略非常适合典型的“读多写少”缓存。volatile-lru的差异在于只处理设置了过期时间的key。有些场景下Redis里同时存了两种数据一种是可以丢的缓存数据设置了TTL一种是不能丢的持久化配置类数据不设TTL。使用volatile-lru可以在内存紧张时只牺牲那些可再生的缓存数据而保住未设置TTL的核心数据。allkeys-random和volatile-random属于比较粗糙的淘汰策略Redis随机挑一批key删掉。如果业务中的key访问概率相差不大、热点不明显随机淘汰的成本最低、性能最好。volatile-ttl的逻辑最直白谁剩余存活时间最短就淘汰谁。这些key本来就是很快要过期的主动提前淘汰对业务的影响最小。但要注意如果设置了TTL的key数量很少这个策略可能会频繁淘汰这批有限的key导致缓存命中率下降。LFU系列是Redis 4.0之后引入的。LRU只看“最近一次访问时间”如果一个key曾经被高频访问但最近一小时没被访问LRU可能把它当成冷数据淘汰掉。LFU则统计访问频次能够更精准地识别哪些才是真正的热点key。在存在明显“冷热分化”的数据分布且热点会长期维持的场景下LFU表现更优。3.3 LRU的近似实现与LFU的计数逻辑标准LRU算法需要维护一个双向链表每次访问都要移动节点在Redis这种单线程模型下成本太高。Redis实际采用的是近似LRU算法——它不会维护全量key的访问时间排序而是随机抽取若干个key从中选出最久没被访问的那个淘汰。这里的“抽样数量”由maxmemory-samples参数控制默认值是5。抽样数越大淘汰结果越接近真实LRU但每次淘汰时消耗的CPU也越多。实测下来默认的5在多数场景下已经足够如果要更精确可以调到10左右。LFU的实现则更有意思。Redis为每个key维护了一个基于对数递增的计数器访问次数越多计数器增长越慢避免高频key的计数器过早饱和。同时计数器还会随时间衰减——如果某个key长时间没被访问它的频率值会逐步减小这样旧的“高频假象”不会永远压住新晋热点。对于这两种算法的选择我的建议是优先用LRU除非你有明确的访问频次统计需求且热点相对稳定否则LFU的优势并不容易发挥出来。4. 配置方法与实操要点4.1 两种配置方式配置文件与命令行Redis的内存淘汰相关配置有两种修改途径一种是修改redis.conf文件后重启生效适合一次性固化配置另一种是用CONFIG SET命令在线修改无需重启即可生效。对于生产环境我的做法是先在线修改观察一段时间确认稳定后再写回配置文件避免直接改文件导致重启后才发现问题。# 在线查看当前配置 127.0.0.1:6379 CONFIG GET maxmemory 1) maxmemory 2) 0 # 在线修改内存上限单位为字节 127.0.0.1:6379 CONFIG SET maxmemory 2gb # 修改淘汰策略 127.0.0.1:6379 CONFIG SET maxmemory-policy allkeys-lru # 修改LRU/LFU抽样数量 127.0.0.1:6379 CONFIG SET maxmemory-samples 10有一点要特别提醒CONFIG SET修改的配置在实例重启后会丢失必须同步修改redis.conf中对应的maxmemory、maxmemory-policy、maxmemory-samples参数保证重启后配置仍然生效。4.2 内存上限设置的容量估算maxmemory的取值不是拍脑袋定的。假设服务器物理内存是16GB操作系统和其他服务占用约4GB那么Redis理论上最多能分配到12GB但实际不能把12GB全部给Redis。原因是Redis执行BGSAVE或AOF重写时会使用fork出来的子进程子进程会通过写时复制技术共享父进程的内存页一旦父进程有大量写入内存占用可能短期内翻倍。如果maxmemory设得太接近物理内存上限触发持久化时可能直接把系统内存耗尽。我的习惯是给Redis预留20%到30%的物理内存作为安全余量尤其在开启了持久化或复制功能的实例上更要把余量放大。从监控角度看仅仅看maxmemory还不够。通过INFO memory命令可以查看used_memory、used_memory_rss、mem_fragmentation_ratio等关键指标# 查看Redis内存信息 127.0.0.1:6379 INFO memory # Memory # used_memory: 987654321 # used_memory_human: 941.72M # used_memory_rss: 1456789012 # mem_fragmentation_ratio: 1.48其中used_memory表示Redis分配器分配的内存总量used_memory_rss表示操作系统实际分配给Redis进程的物理内存。两者之间的比值mem_fragmentation_ratio如果大于1.5说明存在较明显的内存碎片可能需要考虑重启实例或调整内存分配策略。4.3 淘汰策略选择的场景化建议选淘汰策略本质上是回答一个问题当内存不够时你愿意牺牲哪些数据没有绝对正确的答案只有最适合你业务场景的选择。如果Redis纯粹作为缓存来用底层数据在数据库里有备份丢掉任何key都可以从源头恢复那么allkeys-lru或allkeys-lfu是最合理的。它们能最大化缓存命中率。如果Redis里混存了持久化数据和缓存数据那么优先考虑volatile-lru或volatile-ttl这样至少能保证非过期数据不被淘汰。如果坚决不能接受任何key被隐式删除只能选noeviction但相应的你必须做好内存打满后写入失败的监控告警。另外还有一个容易被忽略的因素——淘汰策略对缓存命中率的影响需要结合key的访问模式来评估。我之前在一个限时抢购场景中原本用的allkeys-lru结果大量商品的详情key虽然只在活动开始后短暂高频访问但它们反而把更长期稳定的用户会话key给淘汰了。后来改成volatile-lru后因为商品详情key都设置了TTL会话key没有设置TTL淘汰范围缩小到可过期key命中率立刻恢复正常。5. 常见问题与排查技巧实录5.1 问题一Redis内存突然暴涨刷新后却恢复了我遇到过一个比较典型的情况某个服务的Redis实例内存每晚固定时间点增长到maxmemory附近触发大量淘汰随后又慢慢回落。排查后发现问题不在Redis本身而是业务方在每晚定时批量写入大量带TTL的缓存key这些key写入后马上被定期删除清理但由于写入速度比清理速度快瞬时内存冲到了上限。排查思路是先看INFO stats里的expired_keys和evicted_keys两个指标。expired_keys代表过期删除的key数量evicted_keys代表被内存淘汰的key数量。如果evicted_keys在内存冲高时段暴增说明是淘汰策略在起作用如果expired_keys暴增说明大量key在同一时间过期要检查业务方的TTL设置是否存在集中过期的情况。另一个很多人忽略的点是expired_keys和evicted_keys两个计数器的含义完全不同。曾经有同事把evicted_keys飙升误认为过期删除策略失效整整排查了半天最后发现其实是淘汰策略配置成了allkeys-lrukey虽然设置了TTL但还没到过期时间就被内存淘汰了。5.2 问题二key已经设置了过期时间为什么内存不降这个问题几乎是每次Redis培训必被问到的。原因是惰性删除的局限性对于没有被访问的过期keyRedis只有在定期删除扫描到它时才会清理。如果你的key写入后不再被读取而且定期删除的抽样恰好没选中它那么它会一直留在内存里。要验证这个判断可以观察expired_keys是否在缓慢增长。如果expired_keys在增长但内存没有明显下降说明确实有大量过期key在等定期删除慢慢清理。对于这种场景可以使用SCAN命令配合TTL命令找出大量即将过期或已过期但未删除的key再统一重写这些key来触发删除逻辑。我在实际清理中用的比较多的是这样一个脚本思路# 使用SCAN批量扫描key检查TTL redis-cli --scan --pattern cache:* | while read key; do ttl$(redis-cli ttl $key) if [ $ttl -eq -2 ]; then echo key已过期但未删除: $key fi done这种方式的效率取决于key总量不建议在超大实例上频繁执行。如果内存压力实在太大更快的方案是对实例进行滚动重启让所有key重新从持久化文件加载但这会带来短暂的不可用窗口需要业务侧做好降级方案。5.3 问题三淘汰策略调整后缓存命中率反而下降调整淘汰策略不是一次改完就万事大吉的。我之前把淘汰策略从allkeys-lru改成volatile-lru后发现缓存命中率明显下降。原因是很多热门的业务key根本没有设置TTL它们的生命周期本来由业务方手动管理在allkeys-lru下它们也会参与淘汰竞争改成volatile-lru后这些key不再参与淘汰反而那些设置了TTL的冷数据不断被淘汰导致本该命中的key缓存失效。所以调整淘汰策略前先统计当前Redis中不设置TTL的key占比非常重要。如果未设TTL的key占了大多数而它们又不是真正的核心数据那么volatile系列策略会让淘汰范围变得非常小失去淘汰的意义。反过来如果大多数key都设置了合理的TTLvolatile系列策略能更精准地保护非过期数据。这类问题的排查工具主要依赖redis-cli。通过INFO memory观察内存变化趋势结合--bigkeys分析大key分布再配合业务侧的监控报表来还原缓存命中率的变化基本能定位问题源头。5.4 避坑经验maxmemory-policy设置为noeviction内存打满后会发生什么这是我要专门拿出来说的一个坑。noeviction是默认策略如果你没有显式配置maxmemory-policy那么内存达到maxmemory后所有写入命令都会直接报错。报错信息大概是这样的(error) OOM command not allowed when used memory maxmemory这个错误信息挺好认但问题在于很多业务代码对Redis的写入操作没有做异常捕获。一旦开始报OOM错误业务会连环失败。尤其是缓存穿透场景——缓存写不进去请求全打到数据库数据库压力瞬间飙升最终可能拖垮整个服务。所以如果你用的是noeviction策略必须在监控告警中加上对Redis错误响应的检测一旦出现OOM命令被拒绝立即告警人工介入。如果Redis是纯缓存用途我强烈建议改成allkeys-lru并设置合理的maxmemory让Redis在内存压力下自我消化而不是把压力传递给下游数据库。6. 实操总结如何在自己的Redis实例上验证和调优6.1 从0到1快速验证内存淘汰流程理论讲再多不如自己动手测一遍。下面是我建议的验证步骤花十来分钟就能直观看到淘汰策略是怎么运转的。先准备一个最小化的Redis实例限制内存到64MB# 启动一个限制内存的Redis实例 redis-server --port 6379 --maxmemory 64mb --maxmemory-policy allkeys-lru然后连续写入大量数据观察内存和淘汰计数变化# 写入一个10万条数据的测试 redis-benchmark -t set -n 100000 -d 1024 -r 100000 127.0.0.1:6379 INFO memory # 观察used_memory和maxmemory的关系 # 观察evicted_keys是否在增长跑完之后再写入新的key可以看到虽然总内存已经接近或超过上限但写入并不会报错而是不断有旧key被淘汰。查看evicted_keys的数值如果有明显增长说明淘汰逻辑在生效。接着把策略切换成noeviction再尝试写入一个新key127.0.0.1:6379 CONFIG SET maxmemory-policy noeviction 127.0.0.1:6379 SET key1 value1 (error) OOM command not allowed when used memory maxmemory这个对比实验能让你直观感受到策略差异。很多人在面试里说“noeviction会报错”但实际没触发过脑子里没有画面感。建议每个接触Redis的同学都动手做一次。6.2 如何根据监控数据持续调优配置好淘汰策略并不代表可以一劳永逸。一个成熟的Redis实例应该具备这样一套监控体系内存使用率已用内存/最大内存建议维持在70%到80%以下。淘汰计数evicted_keys的增速如果持续快速增长说明内存压力偏高需要考虑扩容或优化key的TTL。过期计数expired_keys的增速结合惰性删除滞后性判断是否需要调整定期删除频率。命中率通过INFO stats里的keyspace_hits和keyspace_misses计算。止损的阈值没有统一标准关键看业务容忍度。缓存场景下如果命中率低于85%说明淘汰策略可能不太合适或者maxmemory设得太低。另外一个常见调优是调整maxmemory-samples如果实例的key访问模式极不均匀可以尝试把抽样数从5提到10淘汰会更精准代价是更高一点的CPU开销实测绝大部分场景影响不大。6.3 生产环境升级淘汰策略的平滑方案生产环境的Redis不能随意重启。如果需要从一种淘汰策略迁移到另一种建议按这个顺序操作先通过CONFIG SET在线修改maxmemory-policy观察一段时间的监控数据。确认稳定后把redis.conf中的对应配置同步修改防止重启失效。如果涉及maxmemory上限调整同样先在线调整并观察。调整期间关注命中率和淘汰计数两个最敏感的指标出现明显异常随时回滚。这里要特别强调如果你用的是redis主从架构主节点的淘汰策略会通过网络传播给从节点吗答案是不会自动同步。maxmemory和maxmemory-policy属于本地配置不会通过主从复制协议同步。所以改配置时每个节点都要单独修改否则可能出现主从节点行为不一致的情况。6.4 关于过期删除和内存淘汰的后续扩展方向这篇文章讲的是单机Redis的标准行为。如果你用的是Redis集群淘汰逻辑本身不变但每个节点各自维护自己的maxmemory所以集群的总容量是分片的maxmemory之和。这意味着你在规划容量时要把分片数量算进去否则单个分片内存打满会导致整个分片的写入失败。另外Redis 8.0及之后的版本在过期删除和淘汰机制上也有一些演进。比如某些版本优化了lazy free的处理对于一些大key的删除不会完全阻塞主线程。如果你维护的实例版本比较新可以留意一下UNLINK命令和lazyfree-lazy-eviction参数它们能帮你降低淘汰大key时的延迟尖刺。最后再分享一个我个人的实操习惯每次上线新业务先统一设计好key的TTL策略并在代码层查询时主动判断Key是否存在或过期。不要把内存清理完全丢给Redis的过期删除和淘汰机制。这两个机制是兜底方案不应该成为业务数据生命周期管理的主要手段。合理设置TTL、及时清理无用key、预留内存余量才是保证Redis长期稳定运行的根本。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑