问题切入点:内存不足与热点异常
许多团队在Redis使用中同时遇到两类头疼的问题:线上Redis内存占用飙升触发OOM,或者某个key突然被高频访问导致CPU打满、集群倾斜。前者与内存淘汰策略直接相关,后者则源于BigKey和HotKey。这两者并非孤立:一个BigKey可能在内存淘汰时被错误保留导致大量内存浪费;而HotKey一旦被淘汰,又会引发缓存雪崩。因此,将内存淘汰策略与BigKey/HotKey治理放在一起讨论,是运维Redis必须掌握的组合技能。
一、Redis内存淘汰策略详解
当Redis内存使用达到maxmemory限制时,会根据配置的淘汰策略决定如何释放空间。理解每种策略的行为方式与风险场景,是合理配置的前提。
1. 八种策略一览
- noeviction:直接拒绝写入请求,返回OOM错误。适用于不允许丢失任何数据的缓存场景(如session存储)。
- allkeys-lru:从所有key中淘汰最近最少使用的。通用缓存场景首选,因为LRU在多数访问模式下命中率高。
- allkeys-lfu:基于访问频率淘汰。适合读多写少、热key分布稳定的场景(如新闻热点、推荐列表)。
- volatile-lru:仅对设置了过期时间的key执行LRU。用于同时包含持久化数据和临时缓存的数据集。
- volatile-lfu:仅在设置了过期时间的key中按LFU淘汰。结合volatile-lru,需谨慎:若大量key未设过期,可能陷入“踢皮球”困境。
- volatile-ttl:淘汰剩余存活时间最短的key。适用于缓存时间敏感的批处理数据,但TTL分布不均时易导致误淘汰。
- allkeys-random / volatile-random:随机淘汰key。几乎不推荐,因为无法保证命中率,只在内存压力极低或数据等价的特殊场景下使用。
2. 选择策略的关键因素
- 业务容忍度:能否接受数据丢失?noeviction保障写入失败,但不丢已有数据;其他皆可能淘汰用户数据。
- 访问模式:LRU适合幂律分布(少数热点占大部分访问),LFU对突发热点更敏感。
- 过期key占比:若绝大部分key都设了过期,volatile系列与allkeys系列效果相近;若持久化key很多,volatile系列可能让所有淘汰压力集中在过期key上。
- 运维复杂度:allkeys-lru是最保险的默认选择,但需要配合监控确保maxmemory设置合理。
二、BigKey与HotKey的识别
内存淘汰策略设计再好,如果系统中存在BigKey或HotKey,依然会引发性能抖动。必须主动发现它们。
BigKey的诊断工具
- redis-cli –bigkeys:遍历整个数据库,按类型统计最大的key。输出包含Key名称、大小和类型。适合首次排查,但扫描性能会短暂影响线上实例。
- MEMORY USAGE key:精确计算单个key的内存占用(包括序列化开销),比BIGKEYS更准确但需逐个执行。
- SCAN + TYPE/STRLEN/HLEN:自定义脚本扫描,可以分批处理避免阻塞。
- SLOWLOG:如果存在对大key的删除或返回操作,慢查询中可能表现为“DEL bigkey”耗时异常。
HotKey的发现方法
HotKey通常表现为某key的QPS远超其他key,导致Redis实例CPU爬升、网卡打满。常用方法:
- redis-cli –hotkeys(需要开启LFU策略):基于LFU计数器统计访问频率,输出热点key。注意:只有在allkeys-lfu或volatile-lfu下才有效。
- MONITOR命令分析:实时打印所有命令,通过脚本统计出现频率。但MONITOR在高并发下会显著降低性能,建议仅短暂使用(50~100个请求)。
- 客户端侧统计:在客户端(如Jedis、Lettuce)的拦截器中统计key调用次数,上报到监控系统。最推荐的方案,对服务无侵入。
- Proxy或中间件:如果使用Codis、Redis Cluster Proxy等,可通过代理层统计热点。
三、内存淘汰策略对BigKey/HotKey的影响
别以为自动淘汰策略能“智能”解决BigKey和HotKey——恰恰相反,策略可能加剧问题:
- BigKey会被优先保留吗? LRU/LFU只看访问时间和频率,与大小无关。一个1KB的热门key可能频繁出入,但一个100MB的冷门BigKey可能长驻内存,拖累内存使用效率。
- HotKey被淘汰的风险:如果热点key是临时性的(比如秒杀活动),且内存不够,LRU可能将其淘汰,导致后续请求直接穿透到数据库,引发雪崩。
- volatile-ttl误杀:TTL短的key可能是热点数据,却被优先淘汰。
因此,治理BigKey和HotKey不能只靠内存淘汰策略,必须主动干预。
四、BigKey的治理方案
1. 根本解决:拆分
对于大哈希(超过数百个字段),可以拆分为多个小哈希,按业务维度分区(如用户前缀+hash(large_key)%64)。大字符串(如JSON)可拆分为多个片段,或改用HASH结构存储字段。
示例:
将 key: user:1000 的哈希(2000个字段)拆成 user:1000:part0 ~ part3,每个哈希500字段。
2. 数据压缩
对value进行gzip或snappy压缩后存储,读时解压。适合可压缩性高的文本数据(JSON、日志)。注意CPU开销。
3. 使用更合适的数据类型
- 小范围整数集合改用Bitmap或HyperLogLog。
- 大量小对象改用Stream或Sorted Set有序性不必要时可简化。
- 避免Set过期时间均匀且元素顺序不重要时改用List+去重逻辑。
五、HotKey的治理方案
1. 本地缓存兜底
在客户端(应用层)添加LRU缓存,对于热点key,第一次查询后缓存在本地内存中。Redis淘汰导致miss时,本地缓存仍能抗住大部分流量。但要设置合理的TTL,防止数据不一致。
2. 读写分离
利用Redis Replica,将热点key的读请求分散到副本节点。适用于读远超写的情况。
3. 散列key的分布
对于单key热点(如“双十一商品详情”),可以将key拆分为多个副本(如key_1, key_2, key_3),客户端随机访问其中一个,数据写入时同步更新所有副本。读写比例极高时可有效分散CPU压力。
4. 限流降级与熔断
在网关或中间件层对热点key的QPS设置阈值,超出部分直接返回缓存副本(即使过期),或返回默认值。防止Redis实例被击穿。
六、治理最佳实践与验证
排查路线图
- 用redis-cli –bigkeys + MEMORY USAGE 确认BigKey清单。
- 用客户端统计 或 MONITOR 定位HotKey。
- 分析访问模式:读多写少还是读写均衡?决定使用本地缓存还是拆分副本。
- 调整maxmemory策略:通用场景选allkeys-lru或allkeys-lfu(若热点短时则LRU更优)。
- 针对每个BigKey制定拆分方案并实施,观察内存变化。
- 部署HotKey治理组件(本地缓存或副本队列),测试高峰期性能。
验证与回滚
- 监控指标:maxmemory使用率、evicted_keys(淘汰key数量)、keyspace_hits/keyspace_misses命中率、延迟P99。
- 异常回滚:如果拆分类操作导致数据不一致,保留原key的备份。拆分为新key后,旧key仍可存在一段时间(通过双写或迁移)。
- A/B测试:先在少量实例上启用新策略,观察内存和延迟变化再全量。
七、总结
Redis内存淘汰策略和BigKey/HotKey治理看似独立,实际密不可分。正确的淘汰策略能减少因BigKey导致的内存浪费,而主动治理又能让淘汰策略发挥更优效果。推荐初始配置allkeys-lru,配合完善的监控工具(bigkeys、客户端统计、慢查询),定期审查内存分布,及时发现并拆分BigKey、分散HotKey。当线上出现突发热点时,本地缓存是快速救火手段;而长效治理则需要业务侧的合理数据分片。记住:工具解决不了业务设计缺陷,把大key做小、热key分散是每个使用Redis的团队必须养成的习惯。
延伸阅读
