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 校验,主节点提高定期删除频率。