作者:没有四次元口袋的蓝胖
日期:2026-10-15
标签:Redis, 持久化, 淘汰策略, RDB, AOF
Redis知识梳理(5)
写在前面
Redis 数据存在内存 里,速度快,但一旦宕机重启,数据就没了------除非你做了持久化。
持久化是 Redis 的生命线。生产环境不配置持久化,等于把数据放在定时炸弹上。
另外,Redis 内存有限,当内存快满时,需要决定"哪些数据该被淘汰",这就是淘汰策略(Eviction Policy)。
本篇覆盖:
- RDB 持久化(快照)
- AOF 持久化(追加日志)
- 混合持久化(RDB + AOF 结合)
- 数据淘汰策略(8种策略)
一、持久化概述
1.1 为什么需要持久化?
| 问题 | 无持久化的后果 |
|---|---|
| Redis 进程崩溃 | 内存数据全丢 |
| 服务器断电 | 数据全没 |
| 内存不足触发 OOM | 进程被杀,数据消失 |
1.2 Redis 提供的两种持久化方式
| 方式 | 全称 | 核心思想 | 优缺点 |
|---|---|---|---|
| RDB | Redis Database | 定时快照(Snapshot) | 文件小、恢复快 / 可能丢数据 |
| AOF | Append Only File | 记录每次写命令 | 数据完整性好 / 文件大、恢复慢 |
| 对比 | RDB | AOF |
|---|---|---|
| 原理 | 全量快照 | 追加写命令 |
| 数据丢失 | 可能丢最后一次快照后的数据 | 根据刷盘策略,最多丢1秒/每次同步 |
| 文件大小 | 小(二进制压缩) | 大(文本命令) |
| 恢复速度 | 快(直接加载快照) | 慢(重放所有命令) |
| 对性能影响 | fork 子进程时有短暂卡顿 | 持续写磁盘 |
| 适用场景 | 灾难备份 | 数据安全优先 |
二、RDB 持久化详解
2.1 RDB 是什么
RDB(Redis Database)是 Redis 的快照(Snapshot)机制 。在某个时间点,把内存中的所有数据二进制压缩 保存到一个文件(默认 dump.rdb)里。
Redis 内存
│
│ RDB 快照
▼
dump.rdb(二进制文件)
2.2 RDB 的触发方式
1. 手动触发(SAVE / BGSAVE):
bash
# SAVE:主线程阻塞,不推荐生产使用
SAVE
# BGSAVE:fork 子进程,异步执行(生产推荐)
BGSAVE
SAVE vs BGSAVE:
| 命令 | 执行方式 | 对主线程影响 | 生产使用 |
|---|---|---|---|
| SAVE | 主线程执行 | 阻塞 | ❌ 禁止 |
| BGSAVE | 子进程执行 | 不阻塞 | ✅ 推荐 |
2. 自动触发(配置触发规则):
conf
# redis.conf
save 900 1 # 900秒内至少有1个key被修改 → 触发
save 300 10 # 300秒内至少有10个key被修改 → 触发
save 60 10000 # 60秒内至少有10000个key被修改 → 触发
只要满足任一条件,Redis 就会自动执行 BGSAVE。
3. 其他自动触发场景:
- Redis shutdown 时(正常关闭)
- 主从复制时,主节点自动 BGSAVE
- 执行 DEBUG RELOAD 命令
2.3 RDB 的工作流程(fork + COW)
1. Redis 主线程执行 fork()
└─ 创建子进程
2. 子进程把内存数据写入临时 RDB 文件
└─ 主线程继续处理客户端请求(不阻塞)
3. 写完后,用新的 RDB 文件替换旧的
└─ dump.rdb 更新完成
关键技术:fork + COW(Copy On Write)
-
fork()创建子进程时,父子进程共享内存页(不是复制) -
父进程修改内存页时,才会复制该页给子进程(Copy On Write)
-
这样既节省内存,又保证快照一致性
fork 前:
主进程 [数据页A, 数据页B, 数据页C]fork 后(子进程共享父进程内存):
主进程 [数据页A, 数据页B, 数据页C] ← 子进程共享
子进程 [数据页A, 数据页B, 数据页C](共享,不复制)主进程修改数据页A:
主进程 [数据页A', 数据页B, 数据页C](A'是新副本)
子进程 [数据页A, 数据页B, 数据页C](仍是原来的A,保证快照完整)
2.4 RDB 的优点
- 文件紧凑:二进制压缩,体积小,适合备份
- 恢复速度快:直接加载 RDB 到内存
- 性能影响小:fork 子进程处理,主线程不阻塞
- 适合灾难恢复:RDB 文件可以远程传输、归档
2.5 RDB 的缺点
- 可能丢数据:最后一次快照到崩溃之间的数据丢失
- fork 时可能卡顿:数据量大时,fork 操作本身耗时
- 老版本兼容性:RDB 格式版本更新可能导致老版本 Redis 无法加载
🎯 面试高频:RDB 的 fork + COW 机制?
答:BGSAVE 时 Redis 主进程 fork 子进程,父子进程共享内存页;父进程修改数据时才复制该页(COW),这样既省内存又保证快照一致性。
三、AOF 持久化详解
3.1 AOF 是什么
AOF(Append Only File)把每一次写命令 以文本形式追加到文件末尾(默认 appendonly.aof)。
客户端执行:SET user:name "zhangsan"
INCR user:likes
DEL user:token
↓
appendonly.aof 文件追加:
*2\r\n$3\r\nSET\r\n...
*2\r\n$4\r\nINCR\r\n...
*1\r\n$3\r\nDEL\r\n...
核心思想: 记录"做了哪些操作",重启时重放所有命令来恢复数据。
3.2 AOF 的开关
conf
# redis.conf
appendonly yes # 开启 AOF(默认 no)
appendfilename "appendonly.aof"
3.3 AOF 刷盘策略
每次写命令后,AOF 要把数据写到磁盘。有三种策略:
| 策略 | 配置 | 说明 | 性能 | 数据安全 |
|---|---|---|---|---|
| always | appendfsync always |
每次写命令都刷盘 | 慢(每次IO) | 最高(不丢) |
| everysec | appendfsync everysec |
每秒刷盘一次 | 中 | 最多丢1秒 |
| no | appendfsync no |
交给OS决定 | 快 | 可能丢很多 |
生产推荐:everysec(性能和安全的折中)
时间线:
t=0s 写命令A ← 立即追加到 AOF 缓冲区
t=0.3s 写命令B
t=0.8s 写命令C
t=1.0s 刷盘(A、B、C 都落盘了)
t=1.3s 写命令D
t=1.8s 写命令E
t=2.0s 刷盘(D、E 落盘)
如果 1.5s 时崩溃:
→ A、B、C 已落盘,恢复后保留
→ D、E 在缓冲区未落盘,丢失
→ 最多丢1秒数据
3.4 AOF 重写(Rewrite)
问题: AOF 文件越来越大(写命令无限累积),恢复速度越来越慢。
解决: AOF 重写------把当前内存数据重新生成一份"等价的最小 AOF 文件"。
重写前:
SET user:name "zhangsan"
SET user:name "lisi" ← 覆盖了
SET user:name "wangwu" ← 最终值
INCR user:likes
INCR user:likes
INCR user:likes ← 三次INCR
重写后:
SET user:name "wangwu" ← 只保留最终值
SET user:likes 3 ← 合并成最终值
重写的好处:
- 文件更小(去掉冗余命令)
- 恢复更快(重放命令更少)
- 不阻塞主线程(后台子进程完成)
触发方式:
bash
BGREWRITEAOF # 手动触发
# 或配置自动触发
auto-aof-rewrite-percentage 100 # AOF 文件大小翻倍时自动重写
auto-aof-rewrite-min-size 64mb # 最小达到 64MB 才触发
3.5 AOF 的优点
- 数据安全性高:最多丢1秒数据(everysec 模式)
- 可读性好:纯文本命令,方便人工分析和修复
- 支持重写:文件大小可控
3.6 AOF 的缺点
- 文件大:同样数据比 RDB 大很多
- 恢复慢:要重放所有命令
- 性能开销:持续刷盘影响写性能
四、混合持久化(RDB + AOF)
4.1 为什么需要混合?
RDB 和 AOF 各有优缺点,Redis 4.0 引入混合持久化:
-
把 RDB 的全量快照写入 AOF 文件开头
-
把 RDB 之后的增量写命令追加到 AOF 文件末尾
混合 AOF 文件结构:
┌─────────────────────────┐
│ RDB 格式的全量数据(开头)│ ← 快速加载
├─────────────────────────┤
│ AOF 增量命令(末尾) │ ← 保证完整性
└─────────────────────────┘
4.2 混合持久化的工作流
- 触发 BGREWRITEAOF
- 子进程先写 RDB 格式数据到临时文件
- 然后把 AOF 缓冲区中的增量命令追加进去
- 替换旧 AOF 文件
恢复流程:
- Redis 启动时检测到混合 AOF 文件
- 先加载开头的 RDB 数据(快)
- 再重放后面的 AOF 增量命令(完整)
4.3 配置混合持久化
conf
# redis.conf
aof-use-rdb-preamble yes # Redis 4.0+ 开启混合持久化
4.4 持久化方式对比
| 对比 | 只用 RDB | 只用 AOF | 混合持久化(推荐) |
|---|---|---|---|
| 数据安全 | 可能丢最后一次快照后的数据 | 最多丢1秒 | 最多丢1秒 |
| 恢复速度 | 快 | 慢 | 快(先 RDB,再少量 AOF) |
| 文件大小 | 小 | 大 | 中(RDB 压缩 + 增量 AOF) |
| 性能影响 | fork 时卡顿 | 持续刷盘 | 中 |
生产最佳实践:
- 开启 AOF(
appendonly yes) - 刷盘策略用
everysec - 开启混合持久化(
aof-use-rdb-preamble yes) - 定期手动 BGSAVE 一份 RDB 文件作为冷备份,异地保存
五、数据淘汰策略(Eviction Policy)
5.1 为什么需要淘汰?
Redis 内存有限,当设置了 maxmemory 上限时,一旦内存用满,必须有策略决定"淘汰谁"。
conf
# redis.conf
maxmemory 2gb # 设置最大内存
maxmemory-policy allkeys-lru # 设置淘汰策略
如果没设 maxmemory,Redis 会一直用到系统 OOM 被杀。
5.2 八种淘汰策略
Redis 提供 8 种淘汰策略,分为 5 大类:
┌─────────────────────────────┐
│ 内存满了怎么办? │
└──────────┬──────────────────┘
│
┌──────┴──────┬──────────┬──────────┬──────────┐
│ noeviction │ allkeys │ volatile │ allkeys │ volatile
│ (不淘汰) │ 淘汰 │ 淘汰 │ 随机 │ 随机
│ │ │ │ │
│ │ │ │ │
│ │ LRU │ LRU │ LFU │ LFU
│ │ 随机 │ TTL │ TTL │ TTL
│ │ │ │ │
详细分类表
| 策略 | 淘汰范围 | 淘汰依据 | 说明 |
|---|---|---|---|
| noeviction | 不淘汰 | --- | 内存满时拒绝新写入(返回错误) |
| allkeys-lru | 所有键 | 最近最少使用 | 最常用,优先淘汰长期没被访问的 |
| volatile-lru | 设置了过期时间的键 | 最近最少使用 | 只淘汰有过期时间的 |
| allkeys-lfu | 所有键 | 最不经常使用 | Redis 4.0+,按访问频率淘汰 |
| volatile-lfu | 设置了过期时间的键 | 最不经常使用 | 只淘汰有过期时间的 |
| allkeys-random | 所有键 | 随机 | 随机淘汰 |
| volatile-random | 设置了过期时间的键 | 随机 | 只淘汰有过期时间的 |
| volatile-ttl | 设置了过期时间的键 | TTL 最短 | 优先淘汰快过期的 |
核心策略详解
1. noeviction(不淘汰,默认)
- 内存满了,所有写操作报错(OOM)
- 读操作正常
- 生产绝对不要用,否则缓存失效导致数据库雪崩
2. allkeys-lru(最常用)
-
LRU = Least Recently Used(最近最少使用)
-
所有键中,淘汰最近最久没被访问的
-
适用场景: 通用缓存(大部分访问有"热度",热点数据最近被访问)
淘汰流程:
访问 user:1001(最近) ← 留在内存
访问 user:1002(较久前)
访问 user:1003(很久前) ← 优先淘汰
3. allkeys-lfu
- LFU = Least Frequently Used(最不经常使用)
- 所有键中,淘汰访问次数最少的
- 适用场景: 数据访问频率差异大时(比如某些键是"历史数据"很少访问)
LRU vs LFU:
| 对比 | LRU | LFU |
|---|---|---|
| 依据 | 最近一次访问时间 | 访问总次数 |
| 适合场景 | 数据访问有近期热点 | 数据访问有明显频率差异 |
| 缺点 | "偶然访问"会提升优先级 | "历史高频"可能永不淘汰 |
4. volatile-ttl
- 只淘汰"设置了过期时间"的键
- 按剩余 TTL 从小到大淘汰(快过期的先淘汰)
- 适用场景: 缓存键都有过期时间,且希望快过期的先被淘汰
5. allkeys-random / volatile-random
- 随机淘汰
- 一般不用,除非访问模式完全随机
5.3 LRU 实现细节
Redis 的 LRU 不是严格的 LRU,而是近似 LRU:
严格 LRU:维护一个全局链表,每次访问都移动到头部
→ 内存开销大,不现实
Redis 近似 LRU:
1. 随机采样 N 个键(默认5个,可通过 maxmemory-samples 配置)
2. 从 N 个键中淘汰"最久未访问"的那个
3. 采样越多越接近严格 LRU,但 CPU 消耗越大
为什么用近似 LRU?
- 严格 LRU 需要维护每个键的访问链表,内存开销大
- Redis 用随机采样,内存开销几乎为零
- 通过调整采样数量,可以逼近严格 LRU 的效果
5.4 LFU 实现细节
Redis LFU 使用"对数计数器":
- 每个键维护一个 8-bit 计数器
- 访问时计数器按概率递增(越大的计数器越难递增)
- 计数器随时间衰减(防止历史访问一直占优)
5.5 淘汰策略选择
| 场景 | 推荐策略 |
|---|---|
| 通用缓存 | allkeys-lru(默认推荐) |
| 访问频率差异大 | allkeys-lfu |
| 只缓存临时数据 | volatile-ttl 或 volatile-lru |
| 不确定访问模式 | allkeys-random |
| 缓存当永久存储 | noeviction(但危险!) |
生产推荐:allkeys-lru,绝大多数场景下表现良好。
5.6 配置示例
conf
# redis.conf
# 最大内存
maxmemory 2gb
# 淘汰策略
maxmemory-policy allkeys-lru
# LRU 采样数量(默认5)
maxmemory-samples 5
# 内存达到 95% 时主动开始淘汰(可选,提前避免 OOM)
# maxmemory 建议设置为物理内存的 70% 左右
六、持久化与淘汰的配合
生产环境最佳实践:
conf
# 1. 开启 AOF
appendonly yes
appendfsync everysec
# 2. 开启混合持久化
aof-use-rdb-preamble yes
# 3. 自动 AOF 重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 4. 设置内存上限
maxmemory 2gb
# 5. 配置淘汰策略
maxmemory-policy allkeys-lru
# 6. 定期保存 RDB 快照(冷备份)
save 900 1
save 300 10
save 60 10000
七、常见坑点与面试题
7.1 坑点总结
| 坑 | 描述 | 解决方案 |
|---|---|---|
| RDB 数据丢失 | 最后一次快照后崩溃的数据丢失 | 配合 AOF 或频繁 BGSAVE |
| AOF 文件过大 | 没开启重写导致文件膨胀 | 配置自动重写 |
| noeviction 雪崩 | 内存满了拒绝写入 | 配置合适的淘汰策略 |
| fork 大内存卡顿 | 数据量大时 fork 耗时 | 调整 overcommit_memory,或限制 RDB 频率 |
| 混合持久化文件不兼容 | 老版本 Redis 加载失败 | 升级 Redis 版本 |
7.2 面试高频题
Q1:RDB 和 AOF 的区别?
答:RDB 是定时快照(二进制、恢复快、可能丢数据);AOF 是记录写命令(文本、恢复慢、数据安全)。生产推荐混合持久化。
Q2:Redis 的 LRU 和 LRU 严格实现有什么不同?
答:Redis 用近似 LRU,随机采样 N 个键,淘汰其中最久未访问的。节省内存,效果接近严格 LRU。
Q3:生产环境怎么配置 Redis 持久化?
答:
- 开启 AOF(everysec 刷盘)
- 开启混合持久化
- 配置自动重写
- 设置 maxmemory + allkeys-lru
- 定期冷备 RDB 文件到异地
Q4:Redis 内存满了怎么办?
答:根据 maxmemory-policy 淘汰数据,推荐 allkeys-lru 淘汰最近最少使用的键。
思维导图速览
持久化 + 淘汰策略
│
├── RDB(快照)
│ ├── 触发:手动 SAVE/BGSAVE,自动 save 规则
│ ├── fork + COW(Copy On Write)
│ ├── 优点:小、快、适合备份
│ ├── 缺点:可能丢数据
│ └── 生产禁用 SAVE(会阻塞)
│
├── AOF(追加日志)
│ ├── 记录每次写命令
│ ├── 三种刷盘:always/everysec/no
│ ├── 推荐 everysec(最多丢1秒)
│ ├── AOF 重写(压缩文件)
│ └── 优点:数据安全;缺点:大、慢
│
├── 混合持久化(推荐)
│ ├── RDB 全量 + AOF 增量
│ ├── 配置:aof-use-rdb-preamble yes
│ └── 兼顾速度和完整性
│
└── 数据淘汰策略(8种)
├── noeviction(❌禁止使用)
├── allkeys-lru(✅最常用)
├── allkeys-lfu
├── volatile-lru / volatile-lfu
├── allkeys-random / volatile-random
└── volatile-ttl
└── Redis 用"近似 LRU"(随机采样)
写在最后
持久化和淘汰策略是 Redis 的运维核心。生产环境不配置,等于给数据埋雷。
三个核心决策:
- 持久化方式:推荐混合持久化(AOF 开头 RDB + 末尾 AOF 增量)
- 刷盘策略 :生产用
everysec,最多丢1秒数据 - 淘汰策略 :推荐
allkeys-lru,通用且高效
面试中,这几个问题必问:
- RDB 和 AOF 区别
- fork + COW 机制
- 近似 LRU 的原理
- 生产环境怎么配持久化