Redis 内存管理
一、内存上限设置(maxmemory)
1. 默认规则
- Redis 默认不限制内存使用(32 位系统默认上限约 3GB,64 位无默认限制)。
- 生产环境必须显式设置 maxmemory,否则可能因内存耗尽导致服务器 OOM、Redis 进程被系统杀死。
2. 配置方式
(1)配置文件(redis.conf)
maxmemory 10gb
(2)运行时动态修改
# 临时生效(重启后失效)
redis-cli config set maxmemory 10gb
# 永久生效(需同步到配置文件)
redis-cli config rewrite
3. 取值原则
(1)物理内存占比
- 单机部署(Redis + 其他服务):建议设置为物理内存的 50% - 70%(预留内存给系统、Redis 子进程如 RDB/AOF 重写)。
- 独占服务器部署:建议设置为物理内存的 80% - 85%(预留内存给 Redis 的 fork 操作、内存碎片)。
(2)集群场景
每个 Redis 节点的 maxmemory 独立设置,总和不超过集群物理内存总和的 70%。
4. 注意事项
- maxmemory 设置为 0:恢复默认(不限制内存),生产环境严禁这么做。
- 若 Redis 开启了主从复制:主库的 maxmemory 需考虑复制缓冲区(repl-backlog)的内存占用,避免主库内存溢出。
二、过期键清理策略
处理已过期但未删除的 Key。
1. 立即删除
- 机制:靠过期时间自动删除。
- 优点:能保证数据的新鲜度。
- 缺点:会给 CPU 造成额外压力,影响读写性能。
2. 惰性删除(Lazy Delete)
- 机制:访问 Key 时才检查是否过期,过期则删除并返回空(仅清理当前访问的过期 Key)。
- 优点:对性能友好。
- 缺点:对内存不友好(可能导致过期 Key 长期堆积)。
3. 定期删除(Active Delete)
- 机制:主线程每隔 10ms 随机抽取部分过期 Key 检查,删除过期键(默认每次检查 20 个 Key)。
- 优点:平衡性能与内存占用。
- 缺点:会有漏网之鱼(部分过期 Key 未被及时删除)。
三、内存淘汰策略
内存达到 maxmemory 时触发,删除未过期/过期 Key。若策略配置不当,可能导致核心数据被淘汰、缓存击穿等问题。
1. 策略(maxmemory-policy)分类
(1)只淘汰过期键
-
volatile-lru
- 逻辑:从已设置过期时间的键中,淘汰最近最少使用(LRU)的键。
- 适用场景:大部分业务场景(优先保留未过期数据)。
-
volatile-lfu
- 逻辑:从已设置过期时间的键中,淘汰最不常使用(LFU)的键(比 LRU 更精准)。
- 适用场景:访问频率分布不均的场景(如热点商品)。
-
volatile-ttl
- 逻辑:从已设置过期时间的键中,淘汰剩余 TTL 最短的键。
- 适用场景:限时缓存(如秒杀活动、临时会话)。
-
volatile-random
- 逻辑:从已设置过期时间的键中,随机淘汰键。
- 适用场景:无明显访问规律的场景。
(2)淘汰所有键(含未过期)
-
allkeys-lru
- 逻辑:从所有键中,淘汰最近最少使用(LRU)的键。
- 适用场景:缓存作为唯一存储(无持久化)。
-
allkeys-lfu
- 逻辑:从所有键中,淘汰最不常使用(LFU)的键。
- 适用场景:热点数据集中的场景。
-
allkeys-random
- 逻辑:从所有键中,随机淘汰键。
- 适用场景:测试/低优先级场景。
(3)不淘汰(仅拒绝写入)
- noeviction
- 逻辑:达到内存上限后,拒绝所有写入命令(GET/DEL 等读/删命令正常),返回 OOM 错误。
- 适用场景:核心数据不允许丢失(如计数、分布式锁)。
2. 配置方式
(1)配置文件(redis.conf)
# 推荐:缓存场景用 volatile-lru/volatile-lfu;永久缓存用 allkeys-lru
maxmemory-policy volatile-lru
(2)动态修改
redis-cli config set maxmemory-policy volatile-lfu
redis-cli config rewrite # 永久生效
3. 最佳实践
- 缓存场景(大部分键有 TTL):优先选 volatile-lfu(比 LRU 更精准识别低频键),其次 volatile-lru。
- 永久缓存(无 TTL 键居多):选 allkeys-lfu/allkeys-lru,避免随机淘汰核心键。
- 核心数据(不允许丢失):选 noeviction,但需配合监控(内存使用率告警),避免写入失败。
- 禁用策略:volatile-random/allkeys-random 仅适合无业务规律的场景,尽量不用。
4. LRU/LFU 精度优化
Redis 的 LRU/LFU 并非严格的全量遍历(避免阻塞),而是通过采样数(maxmemory-samples)优化精度:
# 采样数(默认 5),值越大越接近真实 LRU/LFU,但消耗 CPU 越高
maxmemory-samples 10
四、内存碎片管理
1. 碎片产生
Redis 运行过程中,因键的频繁增删、数据类型扩容,会产生内存碎片(已分配但未使用的内存),表现为:used_memory(Redis 使用内存)远小于 used_memory_rss(操作系统分配给 Redis 的物理内存)。
2. 碎片率查看
# 查看碎片率(理想值 1.0 - 1.5,超过 1.5 需优化)
redis-cli INFO memory | grep mem_fragmentation_ratio
# 输出示例:mem_fragmentation_ratio:1.8
3. 碎片优化
(1)自动碎片整理
# 开启自动碎片整理(默认开启)
activedefrag yes
# 触发整理的阈值(可根据业务调整)
active-defrag-ignore-bytes 100mb # 碎片超过 100MB 才开始整理
active-defrag-threshold-lower 10 # 碎片率超过 10% 开始整理
active-defrag-threshold-upper 100 # 碎片率超过 100% 时,全力整理
active-defrag-cycle-min 25 # 整理占用 CPU 的最小百分比(25%)
active-defrag-cycle-max 75 # 整理占用 CPU 的最大百分比(75%)
(2)手动碎片整理(应急)
可执行 redis-cli memory defrag 手动触发碎片整理。
(3)预防碎片
- 避免频繁修改大键(如大 Hash/List),减少内存扩容/收缩。
- 合理设置数据结构的初始容量(如 Hash 的 hset 前先用 hreserve 预设容量)。
- 定期重启 Redis(仅应急,重启后碎片清零,但会导致缓存失效,需谨慎)。
五、其他关键内存配置
1. LFU 相关配置
- lfu-log-factor:计数对数因子(影响访问计数的增长速度)。
- lfu-decay-time:计数衰减时间(多久未访问后计数衰减)。
2. 过期键清理相关配置
- hz 100:过期键清理的频率(默认 10,每秒 10 次),值越高清理越及时,但 CPU 消耗越高。
- expire-enabled no:禁用过期键清理(不推荐)。
3. 复制缓冲区内存限制
- repl-backlog-size 10mb:复制积压缓冲区大小(默认 1mb,可根据主从同步流量调整)。
- repl-backlog-ttl 3600:缓冲区空闲超时(默认 1 小时,超时释放)。
4. 内存使用监控
通过 redis-cli INFO memory 查看内存详细信息,关键指标包括:
- used_memory:Redis 已使用内存(字节)。
- used_memory_rss:操作系统分配给 Redis 的物理内存(字节)。
- maxmemory:设置的内存上限。
- mem_fragmentation_ratio:碎片率(used_memory_rss / used_memory)。
- used_memory_peak:内存使用峰值。
六、业务场景适配
1. 过期键清理策略选择
(1)惰性删除 + 定期删除(默认)
- 适用场景:通用电商缓存(商品详情、用户信息)。
- 优势:平衡性能与内存,惰性删除避免全量扫描,定期删除防止过期 Key 堆积。
(2)强化定期删除
- 适用场景:高时效性场景(实时库存、秒杀倒计时)。
- 配置:提高定期删除频率(调整 hz 参数),确保过期 Key 快速清理,避免脏数据。
(3)禁用惰性删除(特殊配置)
- 适用场景:低访问频率但内存敏感场景(归档数据)。
- 优势:减少访问时的过期检查开销,依赖定期删除批量清理。
2. 内存淘汰策略选择
- volatile-lru:电商商品缓存、用户会话缓存(大部分 Key 设过期时间,优先淘汰久未访问的 Key)。
- volatile-lfu:短视频推荐缓存、热点资讯缓存(访问频率差异大,淘汰低频访问 Key)。
- volatile-ttl:秒杀活动缓存、临时验证码缓存(按过期时间优先级淘汰,释放临近过期数据)。
- allkeys-lru:游戏排行榜缓存(无过期时间,淘汰冷数据,保障热点数据常驻)。
- noeviction:金融交易核心缓存、支付信息缓存(数据不可丢失,内存满时拒绝写入,触发告警后人工处理)。
七、常见问题与避坑
1. 设置 maxmemory 后仍 OOM?
- 原因:maxmemory 仅限制 Redis 的数据内存,不包含进程本身、复制缓冲区、AOF 缓冲区等内存。
- 解决:预留更多物理内存,或降低 maxmemory 取值(如从 80% 降到 70%)。
2. 淘汰策略生效但核心键被淘汰?
- 原因:核心键设置了 TTL,却使用 volatile-lru(仅淘汰过期键),导致 Redis 只能淘汰过期的核心键。
- 解决:核心键设置永不过期(不设 TTL),并将策略改为 allkeys-lru,或单独拆分核心键到专属 Redis 实例。
3. 碎片率过高(>2.0)?
- 原因:频繁增删大键、数据类型频繁扩容。
- 解决:开启自动碎片整理,低峰期手动 defrag,或优化数据结构(如合并小键为 Hash)。
八、注意事项
1. 过期键清理调优
- 调整 hz 参数(默认 10):值越高,定期删除越频繁(如高时效场景设为 20),但会增加主线程开销。
- 启用 lazyfree-lazy-expire yes(默认 yes):开启过期 Key 的异步释放,避免清理大 Key 阻塞主线程。
2. 内存淘汰调优
- 合理设置 maxmemory:建议预留 10% - 20% 内存余量,避免频繁触发淘汰策略。
- LFU 策略参数调整:lfu-log-factor(默认 10)越大,计数越稀疏(适配高频访问);lfu-decay-time(默认 1)越大,计数衰减越慢(适配长时热点)。
- 避免混用过期和非过期 Key 时使用 allkeys-lru:可能误删未设过期时间的核心 Key。
九、面试题解析
1. Redis 的过期淘汰策略分为哪两类?各自的核心原理是什么?
- 过期键清理策略:针对已过期的 Key,包括立即删除(过期即删)、惰性删除(访问时删)、定期删除(抽样删)。
- 内存淘汰策略:当内存达到 maxmemory 时触发,根据规则淘汰键(可能是过期或未过期键),如 LRU、LFU、随机淘汰等。
2. Redis 为什么不采用定时全量扫描删除过期 Key?
- Redis 是单线程模型,全量扫描会阻塞主线程,导致所有请求延迟飙升。
- 惰性删除仅清理访问的 Key,可能导致过期 Key 堆积;定期删除通过“随机抽样 + 低频扫描”平衡了性能和内存清理效率,是最优解。
3. LRU 和 LFU 策略的区别?
- 核心差异:LRU 基于“最近最少使用”(侧重访问时间),LFU 基于“最不常使用”(侧重访问频率)。
- LFU 解决的问题:LRU 无法区分“偶尔访问一次的旧 Key”和“低频访问的 Key”,LFU 更精准。
4. 惰性删除可能导致什么问题?如何缓解?
- 问题:过期 Key 长期堆积占用内存;主从节点数据不一致(从节点不执行惰性删除)。
- 缓解方案:提高定期删除的 hz 参数;开启异步过期 Key 释放(lazyfree-lazy-expire);定期手动扫描过期 Key(如 SCAN + TTL 清理);结合内存淘汰策略兜底。
5. 生产环境中 Redis 内存使用率达到 95%,频繁触发 volatile-lru 淘汰,QPS 大幅下降,如何排查和解决?
- 排查步骤:
- 查看 evicted_keys 确认淘汰频率。
- 分析淘汰的 Key 是否为热点数据(通过 INFO stats 和业务日志)。
- 检查过期时间设置是否合理。
- 查看是否存在 BigKey 占用大量内存。
- 解决方案:
- 临时扩容 Redis 内存。
- 调整淘汰策略为 volatile-lfu(保留高频热点)。
- 清理/拆分 BigKey。
- 优化业务逻辑(如缩短非核心 Key 过期时间、降低缓存命中率要求)。
- 长期扩容集群节点(分片)。
6. 秒杀场景中,如何选择过期淘汰策略?为什么?
- 优先选择 volatile-ttl 策略。
- 原因:秒杀场景的缓存 Key(如商品库存、活动状态)均有明确过期时间,需优先淘汰临近过期的临时数据,保障秒杀期间热点商品 Key 不被淘汰;同时需配合调整 hz 参数提高定期删除频率,确保过期的秒杀 Key 快速清理,避免脏数据;秒杀高峰前可临时将 maxmemory 调大,避免触发淘汰策略。
7. noeviction 策略的适用场景?触发该策略后 Redis 会有什么表现?
- 适用场景:核心数据不允许丢失(如计数、分布式锁)。
- 表现:达到内存上限后,拒绝所有写入命令(GET/DEL 等读/删命令正常),返回 OOM 错误。
8. 主从节点的过期淘汰策略执行有什么差异?如何保证主从数据一致性?
- 差异:主节点执行惰性删除和定期删除,从节点仅被动同步主节点的删除操作(不主动检查过期 Key)。
- 一致性保障:
- 主节点删除过期 Key 后,会向从节点发送 DEL 命令。
- 从节点访问过期 Key 时仍会返回数据(直到同步主节点的 DEL 命令)。
- 解决方案:避免依赖从节点的过期 Key 检查,业务层增加 TTL 校验,主节点提高定期删除频率。