3. Redis 内存模型与淘汰策略:内存碎片、maxmemory 配置、LRU/LFU/TTL 淘汰算法原理

3.1 Redis 内存模型全景

Redis 作为内存数据库,其内存管理直接决定服务稳定性与性能。理解内存模型是配置淘汰策略和排查 OOM 的前提。Redis 内存主要由以下四部分组成:

内存区域 说明 占比(典型)
数据内存 存储键值对、过期字典、主从复制缓冲区等核心数据 70% – 85%
程序自身内存 Redis 进程代码、共享对象(整数池、小字符串缓存) 1% – 3%
内存碎片 jemalloc 分配器与操作系统页之间的未使用间隙 5% – 15%
缓冲区与临时内存 客户端输出缓冲区、AOF 重写缓冲区、复制积压缓冲区 5% – 10%
生产经验: 数据内存是唯一可被淘汰策略回收的部分。碎片和缓冲区无法通过淘汰释放,只能通过重启或 MEMORY PURGE 缓解。

3.2 内存碎片成因与治理

Redis 使用 jemalloc 作为默认内存分配器。当键频繁创建、修改、过期或淘汰时,jemalloc 分配的内存块与释放后的空洞无法完全匹配,形成碎片。

  • 碎片率指标: INFO memory 中的 mem_fragmentation_ratio。正常范围 1.0 – 1.5;> 1.5 表示碎片严重;< 1.0 表示使用了 swap(危险信号)。
  • 常见原因:
    • 键值大小频繁变化(如 string append、list 频繁 push/pop)
    • 大量设置了不同 TTL 的键批量过期
    • jemalloc 与操作系统页大小不匹配(4KB vs 64KB)
  • 治理手段:
    1. 低峰期执行 MEMORY PURGE 命令(仅释放空闲内存给 OS,不阻塞)
    2. 重启实例(最彻底,但需主从切换)
    3. 调整 jemalloc 配置(编译时指定 --with-lg-page)
    4. 避免键值大小剧烈波动,使用 hash 结构替代散列 string
注意: MEMORY PURGE 在 Redis 4.0+ 可用,但只对 jemalloc 有效。如果使用 tcmalloc 或系统 malloc,该命令无效果。

3.3 maxmemory 配置与内存水位

maxmemory 是 Redis 的内存硬限制。当数据内存达到该阈值时,Redis 根据配置的淘汰策略(maxmemory-policy)开始驱逐键。配置不当会导致服务不可用或大量逐出。

配置项 推荐值 说明
maxmemory 物理内存的 60% – 70% 预留空间给碎片、缓冲区、主从复制
maxmemory-policy allkeys-lru / volatile-lru 根据业务选择,见下文算法对比
maxmemory-samples 5 – 10 淘汰采样数,越大越精确但越耗 CPU
监控命令: INFO memory 输出 used_memory、used_memory_rss、maxmemory、mem_fragmentation_ratio。建议接入 Prometheus + Grafana 实时看板。

3.4 淘汰算法原理:LRU / LFU / TTL

Redis 提供 8 种淘汰策略,核心算法为三种:LRU(最近最少使用)、LFU(最不经常使用)、TTL(即将过期)。

3.4.1 LRU(Least Recently Used)

  • 原理: 淘汰最近最少被访问的键。Redis 使用近似 LRU(maxmemory-samples 个键中选最久未访问的),而非全量链表,以节省内存。
  • 适用场景: 热点数据访问模式稳定,冷数据长时间不访问。
  • 变体: allkeys-lru(全键空间) vs volatile-lru(仅限设置了 TTL 的键)。

3.4.2 LFU(Least Frequently Used)

  • 原理: 淘汰访问频率最低的键。Redis 4.0+ 引入,使用 Morris Counter 近似计数,避免计数器无限增长。
  • 优势: 能抵抗“周期性热点”场景——一个键昨天高频访问但今天不再使用,LRU 可能误判为热键,LFU 则通过衰减因子降低旧频率。
  • 配置: lfu-log-factor(频率增长对数因子)和 lfu-decay-time(分钟级衰减周期)。

3.4.3 TTL(Time To Live)

  • 原理: 淘汰剩余 TTL 最短的键。仅适用于 volatile-ttl 策略。
  • 风险: 如果大量键 TTL 相同,退化为随机淘汰;且 TTL 值可能被客户端错误设置(如设为 0 或极大值),导致淘汰失效。
策略 算法 适用键空间 内存开销 推荐场景
allkeys-lru 近似 LRU 所有键 低 通用缓存,无 TTL 要求
volatile-lru 近似 LRU 有 TTL 的键 低 混合持久键 + 临时缓存
allkeys-lfu 近似 LFU 所有键 中 访问模式有长尾冷热不均
volatile-lfu 近似 LFU 有 TTL 的键 中 临时热点 + 持久键共存
volatile-ttl TTL 最短 有 TTL 的键 低 严格时效性数据(验证码、会话)
noeviction 无淘汰 — — 写入即报错,仅用于纯读缓存
选型建议: 大部分业务缓存使用 allkeys-lru 即可。如果存在“昨天热点今天冷”的周期性数据,改用 allkeys-lfu 并调低 lfu-decay-time(如 1 分钟)。volatile-ttl 仅当所有键都有精确 TTL 且业务依赖过期时间时使用。
Redis 内存模型与淘汰流程 数据内存 键值对 / 过期字典 主从复制缓冲区 AOF 重写缓冲区 可被淘汰回收 内存碎片 jemalloc 空洞 页对齐间隙 不可淘汰,需 PURGE 程序自身内存 进程代码 共享对象池 不可淘汰 缓冲区 客户端输出 复制积压 不可淘汰 达到 maxmemory 淘汰策略决策(maxmemory-policy) allkeys-lru allkeys-lfu volatile-lru volatile-ttl 近似 LRU 采样淘汰 Morris Counter 频率 仅限 TTL 键 最短 TTL 优先 淘汰结果:驱逐选中的键 → 释放内存 → 恢复写入 如果仍超 maxmemory,继续采样淘汰直到内存低于阈值

3.5 淘汰算法核心机制对比

特性 LRU LFU TTL
淘汰依据 最近访问时间 访问频率 + 衰减 剩余过期时间
内存开销 每个键 24 bit(lru 字段) 每个键 24 bit(lfu 字段:8 bit 频率 + 16 bit 时间戳) 每个键 24 bit(lru 字段复用)
CPU 开销 低(采样比较) 中(频率更新 + 衰减计算) 低(采样比较)
抗扫描能力 弱(一次全扫描会污染 LRU) 强(单次访问频率增量有限) 中(依赖 TTL 设置)
适用数据模式 稳定热点 长尾冷热不均 严格时效性
性能调优参数:
  • maxmemory-samples:默认 5,增大到 10 可提高淘汰精度,但每次淘汰多消耗约 2x CPU。建议不超过 20。
  • lfu-log-factor:默认 10,增大使频率增长更慢,适合高频访问场景。
  • lfu-decay-time:默认 1(分钟),设为 0 表示不衰减(纯频率计数)。

3.6 生产配置示例

# redis.conf 内存相关配置
maxmemory 4gb
maxmemory-policy allkeys-lfu
maxmemory-samples 10
lfu-log-factor 10
lfu-decay-time 1

# 碎片治理
activedefrag yes
active-defrag-threshold-lower 10
active-defrag-threshold-upper 100
active-defrag-ignore-bytes 100mb
active-defrag-cycle-min 5
active-defrag-cycle-max 75
验证命令:
# 查看内存详情
127.0.0.1:6379> INFO memory
# 查看淘汰统计
127.0.0.1:6379> INFO stats | grep evicted
# 手动触发碎片整理(仅当 activedefrag 关闭时)
127.0.0.1:6379> MEMORY PURGE

3.7 常见问题与避坑

  • 误区:maxmemory 设置等于物理内存。 必须预留 20% – 30% 给碎片和缓冲区,否则触发 OOM Killer。
  • 误区:使用 noeviction 作为缓存。 写入满时直接报错,导致业务写入失败。仅适用于纯读缓存或消息队列。
  • 误区:volatile-ttl 能精确淘汰。 如果大量键 TTL 相同,退化为随机淘汰。建议结合 maxmemory-samples 调大。
  • 误区:LFU 一定比 LRU 好。 对于稳定热点,LRU 更省 CPU;LFU 在“冷热交替”场景优势明显。
LRU vs LFU 淘汰行为对比(周期性热点场景) LRU 淘汰(allkeys-lru) 键 A(昨天热点) 最近访问:2 小时前 键 B(今天热点) 最近访问:1 秒前 键 C(冷数据) 最近访问:1 天前 键 D(冷数据) 最近访问:2 天前 淘汰结果:键 C 或 D(最久未访问) 键 A 虽然已冷,但 LRU 仍保留 浪费内存,无法识别“周期性” LFU 淘汰(allkeys-lfu) 键 A(昨天热点) 频率:1000(已衰减至 200) 键 B(今天热点) 频率:50(持续上升) 键 C(冷数据) 频率:1 键 D(冷数据) 频率:2 淘汰结果:键 C(频率最低) 键 A 频率虽高但已衰减,仍保留 LFU 通过衰减识别“周期性热点” 结论:LRU 适合稳定热点,LFU 适合周期性热点 + 长尾冷数据。生产环境推荐 allkeys-lfu 并调低衰减时间。