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)
- 治理手段:
- 低峰期执行
MEMORY PURGE命令(仅释放空闲内存给 OS,不阻塞) - 重启实例(最彻底,但需主从切换)
- 调整 jemalloc 配置(编译时指定
--with-lg-page) - 避免键值大小剧烈波动,使用 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(全键空间) vsvolatile-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 且业务依赖过期时间时使用。
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 在“冷热交替”场景优势明显。