Redis 内存管理深度解析:过期删除与内存淘汰策略 本文综合 Redis 内存分配策略、过期键删除机制、内存淘汰策略、采样机制、LFU 原理及异步删除配置带你彻底搞懂 Redis 内存管理的方方面面。一、内存上限配置maxmemoryRedis 默认不限制内存使用64 位系统生产环境必须主动设置上限防止 Redis 耗尽服务器内存导致 OOM。在 Redis 的配置文件redis.conf中配置maxmemory的大小参数如下所示# redis.conf 配置maxmemory 4gb# 运行时动态修改CONFIG SET maxmemory 4gb CONFIG GET maxmemory建议设为物理内存的 3/4 左右留出空间给操作系统和其他进程。一般小公司设置为 3G 左右实际生产肯定不是 100mb。二、过期键删除策略Redis 中处理过期键有三种理论策略它们在 CPU 和内存之间做出了不同的权衡策略执行方式对 CPU 的影响对内存的影响Redis 是否采用定时删除为每个设置了过期时间的 key 创建一个定时器时间一到立即删除❌ 高维护大量定时器消耗 CPU✅ 最友好过期键第一时间释放❌ 未采用惰性删除每次访问 key 时检查是否过期若过期则删除✅ 低仅在访问时检查❌ 差过期但未访问的 key 会一直占用内存✅ 采用作为兜底定期删除每隔一段时间默认 100ms随机抽取一批设置了过期时间的 key删除其中已过期的✅ 可控通过限制执行时间和扫描数量控制开销✅ 较好能及时清理大部分过期键✅ 采用主力2.1 定时删除为每个 key 设置一个定时器到期立即执行删除。优点内存释放最及时不存在内存浪费。缺点CPU 负担极重。如果有大量 key 设置了不同的过期时间Redis 需要维护大量定时器这在高并发场景下是不可接受的。为什么 Redis 不用Redis 是单线程模型定时器会抢占主线程的执行时间严重影响服务性能。2.2 惰性删除只有当 key 被访问读/写时才检查其是否过期若过期则删除。优点CPU 开销极低无需任何额外维护。缺点如果某些过期 key 永远不会再被访问它们会一直占用内存造成内存泄漏。Redis 中的角色作为兜底机制配合定期删除使用。定期删除漏掉的那些过期 key最终在访问时会被惰性删除清理。2.3 定期删除Redis 内部有一个定时任务serverCron默认每秒运行 10 次可通过hz配置调整。每次执行时会随机抽取一批设置了过期时间的 key删除其中已过期的。优点折中了 CPU 和内存。通过限制每次执行的时间和扫描数量将 CPU 开销控制在一定范围内同时又能及时清理大部分过期键。缺点无法保证所有过期键都在第一时间被删除会有少量残留。Redis 中的角色主力过期清理机制。2.4 Redis 实际采用的方案定期删除 惰性删除定期删除负责主动批量清理控制内存占用。惰性删除负责兜底确保即使定期删除漏掉了一些过期键在访问时也会被清理。这种组合方案在不牺牲 CPU 性能的前提下最大程度地保证了内存不会被过期键无限占用是 Redis 高性能设计中的一个经典权衡。三、内存淘汰策略当实际的存储中超出 Redis 的maxmemory配置大小时Redis 中有淘汰策略把需要淘汰的 key 给淘汰掉整理出干净的一块内存给新的 key 值使用。Redis 6.0 共提供 8 种淘汰策略可按其核心逻辑分为四大类类别策略名核心逻辑一句话总结 不淘汰noeviction内存满了就拒绝新写入直接报错硬拒绝宁可报错也不删数据 随机淘汰allkeys-randomvolatile-random随机挑选 Key 淘汰全凭运气谁被选中谁淘汰⏰ 基于时间的淘汰volatile-ttl淘汰剩余存活时间TTL最短的 Key赶早不赶晚优先清除快要过期的数据 基于频率/时间的淘汰allkeys-lruvolatile-lruallkeys-lfuvolatile-lfu淘汰最久未使用LRU或最不经常使用LFU的 Key优胜劣汰留下访问最频繁/最新的核心数据volatile-xxx 系列只淘汰设置了过期时间的 Key未设置过期时间的 Key 不会被淘汰适合需要持久保留重要数据的场景。3.1 noeviction —— 宁死不屈行为内存超限后所有写入命令SET、LPUSH、SADD等返回错误读命令正常。是否采样❌ 不需要直接拒绝写入。场景金融交易、绝对不允许数据丢失的系统。生产环境很少使用因为一旦写满业务会直接失败。3.2 随机淘汰 —— 纯运气allkeys-random从所有 Key 中随机淘汰。volatile-random仅从设置了过期时间的 Key 中随机淘汰。是否采样❌ 不需要直接调用dictGetRandomKey()随机抽取。实现复杂度 O(1)无额外计算。场景数据访问概率均匀没有明显热点。例如存放临时日志的缓存。3.3 volatile-ttl —— 谁快过期谁先走行为只从设置了过期时间的 Key 中挑选剩余 TTL 最短的淘汰。是否采样✅ 需要采样。默认随机采样 5 个 Key取其中 TTL 最小的淘汰。实现采样个数由maxmemory-samples配置默认 5。场景优惠券、验证码、临时会话等短命数据即将过期的数据价值最低。3.4 LRU 系列 —— 最近最少使用行为淘汰最后一次访问时间最久远的 Key。是否采样✅ 需要采样。默认随机采样 5 个 Key淘汰其中lru时间最小的 Key。实现近似 LRU每个redisObject有 24 位lru字段记录最后访问时间戳秒级精度。采样数量越大越接近真实 LRU但 CPU 开销也越大。场景通用缓存符合二八原则20% 数据承载 80% 请求例如热门新闻、商品信息。缺点容易被一次性查询冲垮热点数据例如批量导出报表。3.5 LFU 系列 —— 最不经常使用4.0行为淘汰访问频率最低的 Key频率会随时间衰减。是否采样✅ 需要采样。默认随机采样 5 个 Key淘汰其中logc最小的 Key经过衰减计算。实现复用 24 位lru字段拆分为16 位 LDT上次衰减时间8 位 LOGCNT对数计数器。场景长期稳定的热点数据如爆款商品、热门文章。避免了 LRU 被突发冷访问挤掉热点的缺陷。四、LFU 深度解析4.1 对数计数器 LOGCNT8 位只能存 0~255如果用线性计数每次访问 1访问 256 次后就饱和了无法区分 1000 次/秒 和 10000 次/秒 的热度。因此 Redis 使用对数增长访问次数越高计数器增加的概率越低。每次访问时以概率p增加logcp 1 / (logc * lfu_log_factor 1)其中lfu_log_factor是可配置参数默认 10。例子lfu_log_factor 10实际访问次数对数计数器 logc说明00初始101前 10 次访问快速上升到 1100~3100 次访问后约 31000~61000 次访问后约 61,000,000~10百万次访问后约 10这样即使实际访问量相差巨大logc也能在 0~255 内合理区分热度。4.2 衰减机制 LDT如果一个 Key 长时间没被访问它的热度应该自然下降。Redis 通过ldt记录上次衰减的时间单位分钟每次访问时计算时间差减去相应的计数。配置参数lfu-decay-time默认 1表示每隔多少分钟衰减 1。例子当前logc 100ldt 1000分钟时间戳1 分钟后当前时间 1001 分钟delta 1→ 衰减 1 次 →logc 99更新ldt 100110 分钟后当前时间 1010 分钟delta 10→ 衰减 10 次 →logc 90更新ldt 1010如果没有衰减热点 Key 会永远占据内存不合理。五、淘汰策略何时触发触发时机执行写命令时同步检查客户端发送写命令如SET、HSET等。Redis 检查当前已用内存是否 ≥maxmemory。若未超过 → 正常执行命令。若已超过 → 进入淘汰流程。循环执行淘汰一次可能淘汰多个 Key直到内存降至maxmemory以下或无法再淘汰如所有 Key 都被尝试过。若淘汰成功则执行原写命令若淘汰后内存仍不足且策略为noeviction则返回错误。注意读操作不会触发淘汰也没有专门的定时线程去跑淘汰只有过期删除有定时任务。淘汰是被动附着在写命令上的。六、异步删除机制DEL vs UNLINK6.1 历史问题早期 Redis4.0淘汰时统一使用同步DEL。如果淘汰的是大 Key如包含数百万元素的 HashDEL会在主线程中遍历释放内存造成长时间阻塞服务不可用。6.2 异步删除命令 UNLINK4.0 引入UNLINK将删除工作拆分为两步主线程从全局字典中摘除该 KeyO(1) 极快。后台线程异步回收实际 value 占用的内存。对于大 KeyUNLINK几乎不阻塞主线程。6.3 淘汰策略的删除方式可配置Redis 提供了配置项lazyfree-lazy-eviction用于控制内存淘汰时使用同步还是异步删除# redis.conflazyfree-lazy-evictionyes# 淘汰时使用 UNLINK异步lazyfree-lazy-eviction no# 淘汰时使用 DEL同步默认生产环境强烈建议设置为yes避免因淘汰大 Key 引发服务抖动。6.4 其他异步删除场景Redis 4.0 还提供了类似的配置控制配置项作用默认值lazyfree-lazy-eviction淘汰策略淘汰时nolazyfree-lazy-expire过期 Key 删除时nolazyfree-lazy-server-del命令内部替换 Key 时如RENAMEnoreplica-lazy-flush主从全量同步清空从库时no建议生产环境将前两项开启。七、采样机制详解哪些策略需要采样策略类型是否需要采样原因noeviction❌ 不需要直接拒绝写入不涉及淘汰allkeys-random/volatile-random❌ 不需要随机淘汰直接调用dictGetRandomKey()volatile-ttl✅ 需要需要从过期 Key 中挑出 TTL 最小的lru系列✅ 需要需要从候选集中挑出最久未使用的lfu系列✅ 需要需要从候选集中挑出访问频率最低的采样参数配置# 默认采样 5 个 Key范围 1~64maxmemory-samples5采样数越大淘汰越精确但 CPU 开销也越大。一般保持默认 5 即可对性能影响极小。八、如何选择淘汰策略需求推荐策略通用缓存不知道选什么allkeys-lru热点数据稳定访问模式长期不变allkeys-lfu需要区分持久数据无过期和临时数据volatile-lru或volatile-lfu数据访问概率均匀allkeys-random临时数据优先淘汰快过期的volatile-ttl绝对不允许丢数据慎用noeviction九、最佳实践总结必须设置maxmemory并选择合适的淘汰策略。推荐策略allkeys-lru或allkeys-lfu如果 Redis 版本 ≥4.0 且业务热点稳定。开启异步删除lazyfree-lazy-eviction yes防止淘汰大 Key 阻塞。监控内存使用率设置告警阈值如 80%及时扩容或优化数据。如果业务有强持久化要求可以考虑主从 哨兵将淘汰策略设为主库淘汰从库不淘汰通过配置slave-read-only。避免使用noeviction作为线上策略除非你能接受写失败。十、常见面试题速答Q: Redis 内存淘汰是在读请求还是写请求触发A: 写请求触发读请求不触发。Q: 淘汰策略是实时扫描全部 Key 吗A: 不是。LRU/LFU/TTL 都是采样淘汰默认 5 个性能可控。Q: 哪些策略不需要采样A:noeviction和所有random系列allkeys-random、volatile-random。Q: 淘汰大 Key 会阻塞 Redis 吗A: 默认会用DEL同步删除会阻塞。应开启lazyfree-lazy-eviction让其异步。Q:volatile-lru和allkeys-lru区别A: 前者只淘汰设置了过期时间的 Key后者淘汰所有 Key。Q: 如何修改淘汰策略A:CONFIG SET maxmemory-policy allkeys-lruQ: LFU 如何解决 LRU 被一次性查询冲垮热点的问题A: LFU 记录访问频率而非最后时间且频率会随时间衰减。一次性查询虽然最近被访问但频率很低不会被误判为热点。Q: Redis 过期键删除和内存淘汰有什么区别A: 过期键删除针对的是设置了过期时间且已到期的 Key由定期删除惰性删除处理内存淘汰针对的是内存达到 maxmemory 上限时的主动清理由淘汰策略处理。两者触发条件和机制不同。希望这篇文章能帮你彻底理清 Redis 内存管理的方方面面在生产环境中游刃有余地配置和管理 Redis 内存。