Redis 系列(六):过期、内存淘汰与内存优化——让 Redis 活得更久

📅 2026/8/10 13:55:40
Redis 系列(六):过期、内存淘汰与内存优化——让 Redis 活得更久
核心目标理解 TTL 过期删除的两种机制惰性 定期及其代价掌握maxmemory八种淘汰策略的语义与选型会用MEMORY USAGE/MEMORY DOCTOR/--bigkeys/INFO memory定位大 key 与内存问题理解淘汰导致的缓存雪崩风险。前置知识完成 Part 2键空间与 TTL与 Part 3每键固定开销 ≈ 50-60 字节。验证环境Redis 8.10.0cygwin 移植版主实例 6379 做过期/内存实验隔离实例 6380 做淘汰策略实验、redis-py 8.1.0、Python 3.11.6、Windows 11。最后复核日期2026-08-07。0. 本篇问题场景两个明明设置了却还是出事内存还在涨每个键都设了 TTL为什么INFO memory的used_memory还是居高不下缓存雪崩某个时间点大量缓存同时失效数据库瞬间被打挂——过期时间本身怎么成了事故源第一个问题的答案在过期删除的懒惰性TTL 到点 ≠ 立即释放内存。第二个问题的答案在淘汰/过期的同步性没有打散的过期时间会让热点同时消失。本篇把过期、淘汰、内存三者串成一条线。1. 过期机制TTL 到点后发生了什么1.1 两种删除策略Redis 对过期键的删除不是到点立刻删除而是两条路径配合策略机制代价惰性删除每次访问键时检查是否过期过期则删除过期但从未再被访问的键会一直占内存定期删除后台任务serverCronhz 频率抽样扫描过期键并删除扫描有成本且是抽样不是全量惰性删除保证读到的永远是活的定期删除兜底没人访问的过期键也要清理。两者都不是到点秒删。1.2 实测一惰性删除$ redis-cli SET lazy_key v EX 1 OK $ redis-cli GET lazy_key # 1 秒内v v 等待 2 秒 $ redis-cli GET lazy_key # 已过期访问时被惰性删除 空 $ redis-cli EXISTS lazy_key 01.3 实测二定期删除activeExpireCycle写入 1 万个 2 秒 TTL 的键后不做任何访问观察INFO stats的expired_keys与dbsize写入 10000 个 2 秒 TTL 键, dbsize: 10000 等待 5 秒期间无任何客户端访问 expired_keys: 1 - 10001 定期删除实际删除了 10000 个起始值 1 来自此前惰性删除实验的计数 5 秒后 dbsize: 0结论定期删除是真实生效的——即使没人访问过期键也会被后台抽样扫描清理。但它是抽样 分批的如果同一时刻到期键特别多秒杀场景、批量设置相同 TTL单次扫描清不完used_memory会在一段时间内虚高。这就是设置了 TTL 内存还在涨的机制解释删除是异步的、有节奏的不是即时的。1.4 过期键对持久化与复制的影响RDB生成快照时已过期的键不写入加载时已过期的键被跳过AOF键过期后追加一条DEL保证重放后一致主从复制过期删除只由主库发起从库不主动删靠主库同步DELPart 10 展开——从库读到已过期但未收到 DEL 的键时返回空值replica-serve-stale-data相关。2. maxmemory 与八种淘汰策略2.1 什么是淘汰当used_memory达到maxmemory上限时Redis 按maxmemory-policy决定拒绝写还是淘汰键腾空间。八种策略按两个维度划分维度含义allkeys-*从所有键里挑淘汰对象volatile-*只从设置了 TTL 的键里挑没有可淘汰的 TTL 键时退化为 noevictionlru/lfu/random/ttl淘汰依据最近最少用 / 最不经常用 / 随机 / 剩余 TTL 最短2.2 实测一noeviction——写满即拒绝隔离实例maxmemory 16mb、maxmemory-policy noeviction写入 4 个 4MB 键后写 big:0 (4MB): True 写 big:1 (4MB): True 写 big:2 (4MB): True 写 big:3 (4MB): True 写 big:4 报错: command not allowed when used memory maxmemory. evicted_keys: 0noeviction是宁可拒绝写入也不丢数据——适合把 Redis 当数据存储的场景缓存场景用它会在写满时直接报错影响业务。2.3 实测二allkeys-lru——淘汰最久未用同一配置改allkeys-lru先写 4 个 4MB 键访问big:1激活它再继续写 2 个访问 big:1激活 LRU: aaaaa 继续写 big:4、big:5 后 evicted_keys: 3 存活: [big:1, big:4, big:5] 淘汰: [big:0, big:2, big:3]LRU 语义完全符合预期被访问过的big:1存活最久未用的big:0/2/3被淘汰。2.4 实测三volatile-lru——只动带 TTL 的键写入 3 个带 TTL 的键 1 个永久键permanent继续写 2 个带 TTL 键继续写 2 个带 TTL 键后 evicted_keys: 2 存活: [permanent, volatile:3, volatile:4] 淘汰: [volatile:0, volatile:1, volatile:2] permanent 是否存活: 1永久键在内存压力下毫发无损——volatile-*系列绝不碰没有 TTL 的键。注意它的陷阱如果带 TTL 的键很少或都被访问过可能无键可淘汰退化为 noeviction 直接报错。2.5 LRU 的近似实现与 LFU严格 LRU 需要维护全量访问时间排序成本太高。Redis 用近似 LRUlru字段Part 3 的 24 bit记录访问时间淘汰时抽样若干键默认maxmemory-samples 5淘汰其中最旧的。代价是近似——抽样可能漏掉真正的冷数据。LFUallkeys-lfu/volatile-lfu记录的是访问频次而非最近时间lru字段的低 8 位存频次、高 16 位存衰减时间频次会随时间衰减。适合访问频率比访问时间更能代表热度的场景如热点商品。选型决策树Redis 承载的是数据不能丢→ noeviction 监控告警 缓存场景 ├─ 所有键都有 TTL→ volatile-lru / volatile-ttl ├─ 有永久热点键 → allkeys-lru避免永久键占满 └─ 频率比时间重要 → lfu 系列 无法接受任何淘汰 → 扩容 maxmemory / 缩键3. 淘汰的连锁反应缓存雪崩淘汰不是免费的被淘汰的键下次访问会回源数据库。三种常见事故事故触发后果缓存雪崩大量键同一时刻过期/被淘汰同时回源数据库打挂缓存穿透查询不存在的 key每次都回源Part 8 详讲缓存击穿单个热点 key 过期瞬间高并发同时重建Part 8 详讲本系列的防偏原则淘汰/过期参数要与回源能力匹配——maxmemory设得过小 allkeys-lru会在流量高峰把整批缓存洗掉雪崩随之而来。缓解手段Part 8 完整展开过期时间加随机抖动、多级缓存、限流降级、热点预加载。4. 内存分析与大 key 治理4.1 四个工具$ redis-cli MEMORY USAGE key # 单键内存value 部分 $ redis-cli MEMORY USAGE key 0 # 含 key 名 $ redis-cli MEMORY DOCTOR # 内存健康体检 $ redis-cli --bigkeys # 采样扫描最大的 key实测--bigkeys在 2 万小键 1 个 5MB 大键 1000 个 List 上的输出[04.55%] Biggest string found so far big:value with 5000000 bytes -------- summary ------- Biggest list found list:333 has 200 items Biggest string found big:value has 5000000 bytes4.2 INFO memory 关键指标used_memory:20,689,174 # 数据实际占用不含碎片 used_memory_human:19.73M used_memory_peak_human:367.87M # 历史峰值——分配器不归还峰值内存 mem_fragmentation_ratio:1.00 # 碎片率1.5 需关注 maxmemory_human:0B # 0 未设上限 maxmemory_policy:noevictionused_memory_peak是最容易被忽略的指标分配器jemalloc/malloc在内存峰值后通常不会把空间归还给 OS——即使数据删了RSS 也回不到峰值以下。MEMORY DOCTOR的实测提示正是这一点Sam, I detected a few issues in this Redis instance memory implants: * Peak memory: In the past this instance used more than 150% the memory that is currently using. The allocator is normally not able to release memory after a peak ...4.3 大 key 的四类危害危害机制阻塞单线程下大 value 的读写/序列化占用命令执行时间带宽GET/LRANGE全量传输大对象拖慢客户端与网络主从延迟大 key 的增量同步传输放大延迟Part 10集群倾斜大 key 所在分片内存/流量失衡Part 11治理手段--bigkeys/--memkeys定期扫描 → 拆 key按字段/按桶→ 压缩序列化 → 必要的大对象移出 Redis 存对象存储。5. 内存优化手段5.1 键粒度是最大的杠杆Part 3 实测每个小键固定开销 ≈ 55.6 字节与 value 大小无关。所以10 万个小键value 1B ≈ 5.6 MB其中 98% 是包装开销同样的数据合并成一个 Hash 的 10 万个字段内存能省 10 倍以上。优化顺序先看键数量再看 value 大小——把多个字段拆成独立 String 键往往是内存翻倍的开端。5.2 小对象编码参数Part 4 的 listpack/intset 编码把大量小对象压进紧凑结构。相关参数CONFIG GET实测本机默认hash-max-listpack-entries 512 set-max-intset-entries 512 set-max-listpack-entries 128 zset-max-listpack-entries 128调大这些阈值可以推迟 hashtable/skiplist 升级、保持紧凑编码——但代价是字段级读写变慢listpack 更新要移动内存。这是内存 vs CPU的权衡不是越大越好。5.3 碎片与共享mem_fragmentation_ratioused_memory_rss / used_memory 1.5 时考虑activedefrag yes或重启分配器归还峰值内存见 4.2小整数共享Part 3 实测本机 8.10 未生效——版本相关不可依赖。6. 版本与环境差异差异点官方 7.4本机 8.10.0cygwin 移植版影响过期删除机制惰性 定期hz10一致本机hz:10实测Part 1maxmemory策略八种八种行为一致本文三个实验均为本机实测共享整数对象0-9999实测未生效Part 3依赖共享省内存的结论需复核版本--bigkeys支持支持输出格式一致淘汰/过期行为在 7.x/8.x 上稳定本文实验数据可直接复用。7. 测试与验收建议测试过期后GET返回 None惰性、批量短 TTL 后dbsize归零定期、maxmemory写满的三种策略行为隔离实例、--bigkeys能发现构造的大 key淘汰实验必须在隔离实例做不要在生产/共享实例上压内存。本篇验收清单能解释TTL 到点 ≠ 立即释放内存并说出两种删除策略能说出noeviction/allkeys-lru/volatile-lru的语义差异并解释为什么 volatile 系列可能退化为 noeviction能解释近似 LRU 与 LFU 的差异及maxmemory-samples的作用会用MEMORY USAGE/MEMORY DOCTOR/--bigkeys/INFO memory四件套定位内存问题能解释键数量比键大小更费内存及 Hash 合并的优化思路能设计 maxmemory 与淘汰策略的选型数据 vs 缓存 vs 热点。8. 常见误区“TTL 到点内存立刻释放”——惰性 定期两种机制都有延迟§1。“volatile-lru 永不报错”——没有带 TTL 的键可淘汰时退化为 noeviction照样 OOM§2.4。“allkeys-lru 会把我的永久数据删掉”——会它是缓存策略承载业务数据时用 noeviction 或 volatile 系列。“删了数据 RSS 就会降”——分配器不归还峰值内存重启才可能回收§4.2。“maxmemory 越大越好”——上限越接近物理内存越危险峰值 碎片 COW 会让实例 OOM 崩溃。“--bigkeys能发现所有大 key”——它是采样扫描漏检率随数据量上升精确分析用MEMORY USAGE或离线分析 RDB。9. 本篇小结回到开篇两个问题“设置了 TTL 内存还在涨”过期删除是惰性 定期两条异步路径批量过期时清理有节奏叠加分配器不归还峰值内存内存下降总是慢于直觉——需要看expired_keys、used_memory_peak而不是只看used_memory。“缓存雪崩从哪来”淘汰/过期策略没有与回源能力匹配——maxmemory过小、过期时间无抖动、热点无预加载都是雪崩的引信。本篇建立的治理模型过期管键的生命周期淘汰管内存上限的兜底优化管每键成本——三层配合Redis 才能在有限内存里稳定运转。下一篇进入并发与架构篇Part 7事务、Pipeline 与 Lua 脚本回答Redis 的原子性到底是什么用秒杀扣库存把 MULTI/WATCH/Lua 串起来。10. 官方资料Key expirationhttps://redis.io/docs/latest/develop/operate/oss_and_stack/management/redis-keyspace-notifications/Eviction policieshttps://redis.io/docs/latest/operate/oss_and_stack/management/eviction/Memory optimizationhttps://redis.io/docs/latest/operate/oss_and_stack/management/optimization/memory-optimization/MEMORY命令族https://redis.io/docs/latest/commands/memory-doctors/