这是我学习 Redis 持久化与内存淘汰时整理的笔记。入门用法见《Redis 快速入门》,分布式锁见《Redis 分布式锁详解》——本文专注数据怎么不丢、内存满了怎么处理两个生产必问点。
一、解决什么问题
Redis 是内存数据库——数据都在内存里,进程一重启,数据全没了。生产环境这是不可接受的(缓存能重建,但登录态、限流计数、分布式锁这种数据丢了会出事)。
所以 Redis 需要持久化:把内存数据落盘,重启后能恢复。两条路线:
| 方案 | 思路 | 一句话 |
|---|---|---|
| RDB | 定时给内存数据拍快照存文件 | "备份整个数据库" |
| AOF | 把每条写命令追加进日志文件 | "重放命令恢复" |
二、RDB:快照持久化
原理
把当前内存里的所有数据,序列化写入 dump.rdb 文件。触发方式:
SAVE # 阻塞式保存(主线程干活,生产别用)
BGSAVE # 后台保存(fork 子进程写,不阻塞主线程)
# 配置自动触发(默认):
save 900 1 # 900 秒内 ≥1 次写 → 快照
save 300 10 # 300 秒内 ≥10 次写 → 快照
save 60 10000 # 60 秒内 ≥1 万次写 → 快照关键机制:fork + Copy-On-Write(COW)——BGSAVE 时 fork 出子进程,父子共享内存页;父进程继续写时,被改的页才复制一份。所以子进程拿到的是 fork 时刻的"一致性快照",不阻塞主线程。
优缺点
- ✅ 恢复快(直接加载 rdb 文件)、文件紧凑(二进制压缩)
- ❌ 两次快照之间的数据会丢(默认 5 分钟粒度,极端丢 5 分钟)
- ❌ fork 大实例时,COW 可能让内存短暂翻倍 + 卡顿
三、AOF:追加式命令日志
原理
每次写命令(SET/DEL/INCR...)追加到 appendonly.aof 文件末尾。恢复时逐条重放。三个刷盘策略(appendfsync):
| 策略 | 行为 | 丢数据 | 性能 |
|---|---|---|---|
always | 每次写都 fsync | 不丢(最多丢一条) | 最慢(磁盘 IO 瓶颈) |
everysec(默认) | 每秒 fsync 一次 | 最多丢 1 秒 | 折中,生产推荐 |
no | 交给操作系统刷 | 丢最多 | 最快 |
AOF 重写(rewrite):日志无限追加会越来越大,且有很多冗余命令(SET k 1 → SET k 2 → SET k 3 只留最后一条)。BGREWRITEAOF 会把内存当前状态压缩成最小命令集重写文件。
Redis 7 实测:AOF 现在是目录结构(multi-part AOF)——
appendonlydir/下三个文件:*.base.rdb(RDB 格式基座)+*.incr.aof(增量命令)+manifest(清单)。这本身就是"混合持久化"的实现:重启先秒载 base.rdb,再重放 incr.aof 增量。
优缺点
- ✅ 数据丢失少(everysec 最多 1 秒)
- ❌ 文件比 RDB 大、恢复慢(要重放全部命令)
四、RDB vs AOF 对比 + 混合持久化
| 维度 | RDB | AOF |
|---|---|---|
| 数据安全 | 丢两次快照间数据(分钟级) | everysec 丢 ≤1 秒 |
| 恢复速度 | 快 | 慢(重放) |
| 文件大小 | 小 | 大(可重写压缩) |
| 主线程影响 | fork 时可能卡 | fsync 时机影响 |
| 适用 | 数据可重建、能容忍分钟级丢失 | 数据重要、要秒级恢复 |
生产实践(Redis 4.0+):appendonly yes 开启混合持久化——AOF 重写时头部用 RDB 格式(秒级加载),后面追加增量命令(少丢数据),兼顾两者。
五、过期删除:数据到期的两种清理方式
键设了 EXPIRE 60 到期后,Redis 怎么删?
| 策略 | 机制 | 问题 |
|---|---|---|
| 惰性删除 | 访问 key 时才检查是否过期,过期则删 | 过期 key 一直没人访问就占着内存 |
| 定期删除 | 每秒抽样检查部分过期 key 删除 | 抽样不保证全删 |
结论:两者配合——惰性删除兜底"访问时正确",定期删除兜底"没人访问的过期键也被清理"。这也是面试常问的"Redis 为什么用惰性+定期,不用定时器":定时器每个 key 一个 timer,大量 key 时 CPU 扛不住。
六、内存淘汰:内存满了怎么办
maxmemory 设上限(如 1GB)后,写入超限时触发淘汰策略(maxmemory-policy):
| 策略 | 行为 | 场景 |
|---|---|---|
noeviction(默认) | 不淘汰,写入报错 | 严禁丢数据的场景 |
allkeys-lru | 所有 key 中淘汰最久未用 | 缓存场景首选 |
volatile-lru | 只淘汰设了过期时间的 key 中 LRU | 有"永久 key"要保留时 |
allkeys-random / volatile-random | 随机淘汰 | 淘汰无所谓 |
allkeys-lfu / volatile-lfu | 按访问频率淘汰(LFU) | 访问热度差异大的场景 |
注:Redis 的 LRU 是近似 LRU(抽样 N 个再比 LRU,不是全量精确),
maxmemory-samples控制抽样数(默认 5,调大更精确更耗 CPU)。
七、实跑验证(Docker Redis 实测)
# 启动 Redis 容器(挂载数据目录便于看文件)
docker run -d --name redis-persist -p 16379:6379 \
-v redis-data:/data redis:7-alpine① RDB 触发与恢复:
redis-cli -p 16379 set user:1 BinMaker
redis-cli -p 16379 save # 手动触发快照
docker exec redis-persist ls /data # 看到 dump.rdb
docker restart redis-persist # 重启
redis-cli -p 16379 get user:1 # BinMaker(数据恢复 ✅)② AOF 文件内容(开启后):
redis-cli -p 16379 config set appendonly yes
redis-cli -p 16379 set user:2 Alice
docker exec redis-persist cat /data/appendonly.aof | head # 看到 *3\r\n$3\r\nset...③ 过期删除行为:
redis-cli -p 16379 set session:1 token EX 5
redis-cli -p 16379 ttl session:1 # 5 秒后 → -2(已过期)
redis-cli -p 16379 get session:1 # nil(惰性删除触发 ✅)④ 内存淘汰:
redis-cli -p 16379 config set maxmemory 10mb
redis-cli -p 16379 config set maxmemory-policy allkeys-lru
redis-cli -p 16379 info stats | grep evicted_keys # 淘汰计数增长小结
- 持久化两兄弟:RDB 快照(恢复快、丢分钟级数据)、AOF 命令日志(秒级恢复、文件大)——生产用混合持久化
- 过期清理:惰性删除(访问时)+ 定期删除(抽样)配合,不用定时器
- 内存淘汰:8 种策略,缓存场景
allkeys-lru首选;禁丢数据用noeviction - 下一篇可以看《Redis 哨兵与集群》(高可用:主从复制、哨兵故障转移、集群分片)——单机持久化解决"重启不丢",集群解决"一台挂了怎么办"。
验证说明:本文全部用 Docker Redis 7 容器实跑验证——① RDB:
SAVE生成 dump.rdb、重启后GET user:1恢复 ✅;② AOF:appendonlydir/*.incr.aof里能看到 RESP 格式的SET user:2 Alice命令 + base.rdb/incr.aof/manifest 三文件结构 ✅;③ 过期:TTL返回 -2、GET返回 nil(惰性删除)✅;④ 淘汰:maxmemory 10mb + allkeys-lru下redis-benchmark写入 64MB,evicted_keys: 478,淘汰后继续写入正常 ✅。
