Redis知识梳理(5)

作者:没有四次元口袋的蓝胖

日期:2026-10-15

标签:Redis, 持久化, 淘汰策略, RDB, AOF

Redis知识梳理(5)

写在前面

Redis 数据存在内存 里,速度快,但一旦宕机重启,数据就没了------除非你做了持久化。

持久化是 Redis 的生命线。生产环境不配置持久化,等于把数据放在定时炸弹上。

另外,Redis 内存有限,当内存快满时,需要决定"哪些数据该被淘汰",这就是淘汰策略(Eviction Policy)。

本篇覆盖:

  1. RDB 持久化(快照)
  2. AOF 持久化(追加日志)
  3. 混合持久化(RDB + AOF 结合)
  4. 数据淘汰策略(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 的优点

  1. 文件紧凑:二进制压缩,体积小,适合备份
  2. 恢复速度快:直接加载 RDB 到内存
  3. 性能影响小:fork 子进程处理,主线程不阻塞
  4. 适合灾难恢复:RDB 文件可以远程传输、归档

2.5 RDB 的缺点

  1. 可能丢数据:最后一次快照到崩溃之间的数据丢失
  2. fork 时可能卡顿:数据量大时,fork 操作本身耗时
  3. 老版本兼容性: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. 数据安全性高:最多丢1秒数据(everysec 模式)
  2. 可读性好:纯文本命令,方便人工分析和修复
  3. 支持重写:文件大小可控

3.6 AOF 的缺点

  1. 文件大:同样数据比 RDB 大很多
  2. 恢复慢:要重放所有命令
  3. 性能开销:持续刷盘影响写性能

四、混合持久化(RDB + AOF)

4.1 为什么需要混合?

RDB 和 AOF 各有优缺点,Redis 4.0 引入混合持久化:

  • 把 RDB 的全量快照写入 AOF 文件开头

  • 把 RDB 之后的增量写命令追加到 AOF 文件末尾

    混合 AOF 文件结构:

    ┌─────────────────────────┐
    │ RDB 格式的全量数据(开头)│ ← 快速加载
    ├─────────────────────────┤
    │ AOF 增量命令(末尾) │ ← 保证完整性
    └─────────────────────────┘

4.2 混合持久化的工作流

  1. 触发 BGREWRITEAOF
  2. 子进程先写 RDB 格式数据到临时文件
  3. 然后把 AOF 缓冲区中的增量命令追加进去
  4. 替换旧 AOF 文件

恢复流程:

  1. Redis 启动时检测到混合 AOF 文件
  2. 先加载开头的 RDB 数据(快)
  3. 再重放后面的 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 持久化?

答:

  1. 开启 AOF(everysec 刷盘)
  2. 开启混合持久化
  3. 配置自动重写
  4. 设置 maxmemory + allkeys-lru
  5. 定期冷备 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 的运维核心。生产环境不配置,等于给数据埋雷。

三个核心决策:

  1. 持久化方式:推荐混合持久化(AOF 开头 RDB + 末尾 AOF 增量)
  2. 刷盘策略 :生产用 everysec,最多丢1秒数据
  3. 淘汰策略 :推荐 allkeys-lru,通用且高效

面试中,这几个问题必问:

  • RDB 和 AOF 区别
  • fork + COW 机制
  • 近似 LRU 的原理
  • 生产环境怎么配持久化
相关推荐
用户302822530681 小时前
你的 API 只返回 30 条,为什么数据库还是被打满了?
数据库·后端·架构
for_ever_love__1 小时前
半同步复制:数据安全和服务可用之间怎么权衡
redis·mysql·缓存·一致性·数据·半同步复制·方式
用户713874229001 小时前
一文读懂 AWS IAM:从工牌、钥匙到统一登录
数据库
tryxr1 小时前
如何优化sql
java·数据库·sql·数据库优化
曦夜日长1 小时前
MySQL系统篇,安装部署(一):CentOS 7 下从卸载到登录的完整实战
数据库·mysql·centos
路上有只喵1 小时前
认识立创商城元器件 · 贴片电容 MLCC · 国巨CC0603KRX7R9BB104规格书解读4
数据库
微学AI1 小时前
260多条异构链路背后,KFS如何同步数据库结构变化
数据库
努力进修1 小时前
多源异构数据库同步难题:DDL 结构变更自动化解决方案
运维·数据库·自动化
Anastasiozzzz1 小时前
InnoDB 为什么不用跳表,Redis 为什么不用 B+树?一场“磁盘”与“内存”的终极对决
数据库·redis·b树