Skip to content
第 3 章 ⏱ 12 分钟阅读

第 3 章:Redis 持久化 ​

学习目标 ​

  • 理解 RDB 和 AOF 两种持久化原理
  • 学会配置和选择持久化策略
  • 掌握混合持久化(RDB + AOF)
  • 了解 Redis 重启恢复流程

一、为什么需要持久化 ​

Redis 数据在内存,进程挂掉就丢了。持久化把数据写到磁盘,重启后能恢复。生产环境必须开启持久化,否则:

  • 主从全挂 = 全丢
  • 即使有主从,首次部署从空开始也是灾难

二、RDB(快照) ​

定时把内存数据整体写到 .rdb 文件。

触发方式 ​

bash
# 配置自动触发(redis.conf)
save 900 1       # 900 秒内至少 1 个 key 变更
save 300 10      # 300 秒内至少 10 个
save 60 10000    # 60 秒内至少 10000 个

# 手动触发
SAVE            # 同步,阻塞主线程(生产禁用)
BGSAVE          # fork 子进程异步,推荐

save 900 1 怎么读:

"Redis 像个保安,守着门口,数客户进出的次数"

900 秒 = 保安值班的"一轮时长"(15 分钟)
1     = 这轮只要进来过 1 个客户 → 拍快照

00:00 保安开始值班
00:01 set user:1 "tom"   ← 进出 1 次
00:03 set user:2 "jerry" ← 进出 2 次
00:15 值班结束:本轮 ≥ 1 次 → 拍快照 ✅
00:15 开始新一轮,本轮 0 次 → 不拍

为什么3 条配置?:给3 个保安,不同时间段不同严格度(任一达标就拍):

save 60 10000  ← 1 号保安:1 分钟内 10000 次才拍(很宽松)
save 300 10    ← 2 号保安:5 分钟内 10 次才拍
save 900 1     ← 3 号保安:15 分钟内 1 次就拍(很敏感)

配置触发永远走异步(BGSAVE)—— 不是 SAVE。Redis 设计上已经替你做了选择,因为配置本来就要"经常触发",如果走同步 SAVE,主线程会一直阻塞 → 服务废掉。

方式主线程触发
SAVE 命令阻塞(同步)手动
BGSAVE 命令异步(fork)手动
save 900 1 配置异步(=BGSAVE)自动

自动触发 + 手动触发不冲突:

自动(配置)手动(运维)
谁触发Redis 内部你
时机达到阈值需要时
适合日常备份上线前/迁移/关机

手动触发场景:

bash
# 上线前:怕出问题先拍
redis-cli BGSAVE

# 跨实例迁移
redis-cli -h A BGSAVE
scp A:/data/dump.rdb B:/data/

# 关机前
redis-cli SHUTDOWN              # 默认关机前拍
redis-cli SHUTDOWN NOSAVE       # 关机不拍

SAVE vs BGSAVE 比喻:

SAVE   = 经理亲自下场盘点 → 期间关门不营业(生产禁用)
BGSAVE = 经理派助理盘点 → 营业照常(fork 阶段会卡,但比 SAVE 轻)

实战调小内存卡顿:

bash
# 大内存项目,改宽松点,降低 fork 频率
save 900 1          # 默认值
save 3600 100       # 加一条:1 小时 100 次才拍
# ↑ 从"几十秒一拍"降到"几小时一拍",fork 卡顿频率大幅降低

优缺点 ​

优点缺点
文件紧凑,适合备份会丢最近一次快照之后的数据
恢复速度快大数据集 fork 会卡顿

⚠️ 坑 1:BGSAVE 用 fork 创建子进程,内存越大 fork 越慢(20GB 内存可能卡几秒),期间阻塞所有请求。


只用 RDB 最大的缺点:丢数据。

纯 RDB:
  12:00 拍快照
  12:05 Redis 挂了
  → 12:00 之后的数据全丢(5 分钟工作没了)

纯 AOF(everysec):
  12:05 Redis 挂了
  → 最多丢 1 秒

RDB 丢多少看 save 配置:

bash
save 60 10000    # 默认:1 分钟 10000 次才拍 → 最多丢 1 分钟
save 300 10      # 5 分钟 10 次才拍 → 最多丢 5 分钟
# 写严(丢少):save 1 1 → 最多丢 1 秒,但 fork 频繁卡内存

其他缺点(次要):

缺点影响
丢数据主要缺点
大内存 fork 卡BGSAVE 时阻塞(见坑 1)
不适合写多场景频繁 fork 浪费

什么时候只用 RDB 没问题?

bash
# ① 纯缓存(丢了不心疼,如 session 临时数据)
# ② 写很少 + 内存小(如配置中心)
# ③ 备份专用(主实例开 AOF,这个纯备份)

生产实际:两个都开(第 5 节混合持久化)。

三、AOF(追加日志) ​

把每条写命令追加到 .aof 文件,重启时回放恢复。

开启 ​

bash
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec     # 每秒刷盘(推荐)
# appendfsync always     # 每条都刷,性能差
# appendfsync no         # 由 OS 决定,可能丢很多

一句话讲清 AOF:

把"每条写命令"全记下来,重启时照着"重新敲一遍"恢复数据。

SET user:1 "tom"   → 写到 aof
SET user:2 "jerry" → 写到 aof
SET user:1 "alice" → 写到 aof
 ↓ Redis 挂了 → 重启 → 读 aof → 重敲一遍 → 恢复

appendfsync 三档(类比记账):

模式类比优点缺点
always改一份给一份成绩不丢数据慢,机械盘 QPS 卡到几百
everysec攒 1 秒统一公布快 + 最多丢 1 秒极端情况丢最后 1 秒
no想起来再记最快宕机可能丢很多

生产标准配置(就这 3 行):

bash
appendonly yes
appendfsync everysec
# 其他默认就好

重写(AOF 压缩) ​

AOF 文件会越来越大,Redis 提供 BGREWRITEAOF 自动压缩:

bash
# 自动触发
auto-aof-rewrite-percentage 100   # 比上次重写增长 100% 触发
auto-aof-rewrite-min-size 64mb    # 文件至少 64MB 才触发

# 手动
BGREWRITEAOF

重写思路:根据当前数据状态,生成最简命令(一条 SET k v 替代一万次变更)。

自动重写(Redis 默认就开):

bash
auto-aof-rewrite-percentage 100    # 增长比例:翻倍才触发
auto-aof-rewrite-min-size 64mb    # 最小文件:小文件不重写

两个阈值配合:

00:00 AOF 50MB  → 不重写(< 64MB)
01:00 AOF 80MB  → 80÷50 = 160% > 100% 且 > 64MB → 自动重写
02:00 AOF 60MB  → 60÷50 = 120% > 100% 但 < 64MB → 不重写
03:00 AOF 70MB  → 触发重写

手动触发(运维场景):

bash
redis-cli BGREWRITEAOF# 上线前、迁移前手动压一次

实战 percentage 100 + min-size 64mb 是 Redis 默认,不用改。

优缺点 ​

优点缺点
最多丢 1 秒数据文件大,恢复慢
可读,易调试写放大,QPS 受限

⚠️ 坑 2:appendfsync always 写入磁盘,机械盘下 QPS 可能掉到 1000 以下,生产不要用。

四、RDB vs AOF 对比 ​

维度RDBAOF
持久化粒度时间间隔每条命令
数据安全可能丢 N 分钟最多丢 1 秒
文件大小小大
恢复速度快慢
写性能高较低

不是二选一,可以同时开:

Redis 持久化 = 两个独立开关:
  RDB:save 60 10000      # RDB 开关(默认开)
  AOF:appendonly yes     # AOF 开关(默认关)
配置适合
只 RDB备份、灾备
只 AOF数据安全
两个都开生产推荐(混合持久化)

两个都开时,Redis 重启用谁?

AOF 数据更全 → 优先用 AOF 恢复
RDB 数据更老 → 被忽略(⚠️ 见坑 4)

生产推荐配置(混合持久化):

bash
save 900 1               # RDB 自动触发
appendonly yes            # AOF 开启
aof-use-rdb-preamble yes # AOF 文件前半段用 RDB 格式(混合)
appendfsync everysec

混合效果:AOF 文件 = RDB 全量 + AOF 增量,重启先快加载 RDB,再回放 AOF。

新项目口诀:两个都开,别纠结。

五、混合持久化(Redis 4.0+ 推荐) ​

RDB 做全量,AOF 记录增量,兼顾速度和安全。

bash
# redis.conf
aof-use-rdb-preamble yes

原理:重写 AOF 时,文件前半段是 RDB 格式全量数据,后半段是 AOF 格式增量命令。重启先加载 RDB,再回放 AOF 增量。

用"相册+日记"类比:

相册 = RDB(快照):存的是"现状"的照片
日记 = AOF(追加):按天记的流水账,越来越长

没混合前(纯 AOF):
  你的日记本:全是流水账(10 万条记录)
  → 搬家时:从头读 10 万条(慢)

混合后(aof-use-rdb-preamble yes):
  你的日记本改成:
  ┌──────────────┬──────────────┐
  │ 12月31日的照片 │ 12月31日的日记 │
  │ (拍了一张全家福) │ (之后的几条小事) │
  │ ← 反映"现在"   │ ← 反映"之后"   │
  └──────────────┴──────────────┘
  ↑ 1 张照片(快)   ↑ 几条日记(准确)
  搬家时:看照片 + 看日记 = 又快又全

所以这一行配置就是:

bash
aof-use-rdb-preamble yes
# = AOF 文件的前半段用"快照"形式存(RDB 格式)
# = 后半段还是按命令形式存

为什么这么设计?

问题:纯 AOF 是流水账,恢复慢(读 10 万条命令)解法:
  把"当前状态"拍成图(RDB 格式)写到 AOF 前半段
  之后的新增改动还是 AOF 格式追加在后半段
  重启恢复:看图(快)+ 看日记(补全)

Redis 4.0+ 默认 yes,没缺点,所以都开。

生产推荐配置:

bash
appendonly yes
aof-use-rdb-preamble yes
appendfsync everysec

六、Java 实战:配置 RedisTemplate ​

重要说明:持久化是 Redis 服务端的事(改 redis.conf),Java 端只管"怎么连 + 怎么序列化"。两者分工:

┌─────────────┐
│  redis.conf  │ ← 决定"数据怎么存盘"(持久化)
│  (服务端)    │
└──────┬──────┘
       │ TCP 连接
       ↓
┌─────────────┐
│ Java 客户端   │ ← 决定"怎么发命令、怎么序列化"
└─────────────┘

redis.conf 持久化配置(回顾):

conf
# 1. RDB 触发规则
save 900 1
save 300 10
save 60 10000

# 2. AOF 开关 + 刷盘策略
appendonly yes
appendfsync everysec

# 3. 混合持久化
aof-use-rdb-preamble yes

# 4. 文件名 + 路径
appendfilename "appendonly.aof"
dir /var/lib/redis

Java 端配置(application.yml 配连接):

yaml
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: secret

选 StringRedisTemplate 而不是 RedisTemplate:大多数业务只存字符串 / 数字,直接用 Spring Boot 默认提供的,不用写 @Configuration。

java
@Service
public class UserService {

    // ① 直接注入(Spring Boot 自动配置,不用写 Bean)
    @Autowired
    private StringRedisTemplate redis;

    // ② 存字符串
    public void saveName(Long id, String name) {
        redis.opsForValue().set("user:" + id + ":name", name);
    }

    // ③ 取字符串
    public String getName(Long id) {
        return redis.opsForValue().get("user:" + id + ":name");
    }

    // ④ 计数(自增)
    public Long incrViews(Long articleId) {
        return redis.opsForValue().increment("article:" + articleId + ":views");
    }

    // ⑤ 带过期时间
    public void saveCode(String phone, String code) {
        redis.opsForValue().set("code:" + phone, code, Duration.ofSeconds(60));   // 60 秒过期
    }

    // ⑥ 删
    public void del(Long id) {
        redis.delete("user:" + id + ":name");
    }
}

两个 Template 对比(选型用):

StringRedisTemplateRedisTemplate<Object,Object>
Key/Value 类型String任意(可存对象)
序列化String(原样)JDK 二进制(乱码,不可读)
配置不用写要配 Jackson2JsonRedisSerializer
适合字符串/数字(主流)复杂对象(需要 JSON)

实战选型口诀:

业务只存字符串、数字 → StringRedisTemplate(默认,够用)
业务要存 User/Order 等对象 → RedisTemplate + Jackson(JSON 序列化)

要存对象(配 JSON 序列化):

java
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> tpl = new RedisTemplate<>();
        tpl.setConnectionFactory(factory);                  // ① 连 Redis

        // ② key 用 String("user:1" 这种)
        tpl.setKeySerializer(new StringRedisSerializer());
        // ③ value 用 JSON(对象→{"name":"tom"})
        tpl.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
        // ④ hash 字段也用 String
        tpl.setHashKeySerializer(new StringRedisSerializer());
        // ⑤ hash 字段值用 JSON
        tpl.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));

        tpl.afterPropertiesSet();                           // ⑥ 必须调,初始化
        return tpl;
    }
}

Java 触发持久化命令(运维场景,生产一般用 redis-cli):

java
@Service
public class PersistenceService {

    @Autowired
    private StringRedisTemplate redis;

    // ① 手动拍快照(BGSAVE)
    public void takeSnapshot() {
        redis.execute((RedisConnection conn) -> {
            conn.bgsave();                                  // 触发 BGSAVE
            return null;
        });
    }

    // ② 查上次快照时间
    public long lastSave() {
        return redis.execute((RedisConnection conn) -> {
            return conn.lastSave();                         // 返回时间戳(秒)
        });
    }

    // ③ 手动触发 AOF 重写
    public void rewriteAof() {
        redis.execute((RedisConnection conn) -> {
            conn.bgRewriteAof();                            // BGREWRITEAOF
            return null;
        });
    }

    // ④ 看持久化状态
    public String persistenceInfo() {
        return redis.execute((RedisConnection conn) -> {
            Properties info = conn.info("persistence");     // INFO persistence
            return info.toString();
        });
    }
}

实战一般不用 Java 触发持久化,运维层面 redis-cli BGSAVE 更直观。

七、监控持久化状态 ​

bash
INFO persistence        # 看 RDB/AOF 状态
LASTSAVE               # 上次 RDB 成功时间(秒级时间戳)

⚠️ 坑 3:不要相信"Redis 是内存数据库不需要持久化" —— 主从切换的瞬间、从节点首次同步时,没持久化就意味着丢全部历史数据。

八、备份与恢复 ​

bash
# 备份
cp /var/lib/redis/dump.rdb /backup/redis-2024-01-01.rdb

# 恢复:把 RDB 文件放到 Redis 数据目录,重启 Redis
docker run -v ./dump.rdb:/data/dump.rdb redis:7-alpine

⚠️ 坑 4:恢复时 Redis 必须是干净状态,数据目录里不能同时有 RDB 和 AOF(AOF 优先级高,RDB 会被忽略)。

关键区分:混合持久化 vs 两个独立文件:

混合持久化(aof-use-rdb-preamble yes):
  /data/appendonly.aof ← 一个文件,内部两种格式
  ┌──────────────────┬──────────────┐
  │ RDB 格式(全量)     │ AOF 格式(增量)│
  └──────────────────┴──────────────┘
  → Redis 自动管理

坑 4 的"两个文件":
  /data/dump.rdb         ← 独立 RDB 备份
  /data/appendonly.aof   ← 独立 AOF 日志
  → AOF 优先, RDB 被忽略

为什么 AOF 优先?

RDB = 某一刻的快照(12:00)
AOF = 12:00 ~ 12:05 所有命令
→ AOF 数据更全,所以优先用 AOF 恢复

实操恢复(RDB 备份):

bash
# 1. 关 Redis
redis-cli SHUTDOWN

# 2. 删除 AOF(关键!)
rm -f /var/lib/redis/appendonly.aof

# 3. 放 RDB 备份
cp /backup/redis-2024-01-01.rdb /var/lib/redis/dump.rdb

# 4. 启动
redis-server /etc/redis.conf
# → 只用 RDB 恢复(没 AOF 干扰)

为什么 docker 挂载容易踩?

bash
docker run -v ./dump.rdb:/data/dump.rdb redis:7-alpine
# ↑ 但镜像里 /data/ 已经有 appendonly.aof(镜像层带的)
# → 容器里两个文件都有
# → RDB 被忽略,备份"白备了"

# 解决:先起临时容器,再放数据
docker run --rm -d --name temp-redis redis:7-alpine
docker exec temp-redis redis-cli SHUTDOWN NOSAVE
docker run -v ./dump.rdb:/data/dump.rdb redis:7-alpine

记忆口诀:

混合持久化 = 一个文件内部两种格式 ✅
备份恢复 = RDB 和 AOF 不能同时存在 ❌

九、本章小结 ​

要点关键
RDB定时快照,文件小、可能丢数据
AOF追加日志,最多丢 1 秒
混合持久化Redis 4.0+ 推荐,兼得两者
配置appendonly yes + aof-use-rdb-preamble yes
everysec生产默认刷盘策略
备份定期拷贝 RDB 文件到异地

动手练习 ​

  1. 启动 Redis,执行 CONFIG SET save "" 关闭 RDB,然后 BGSAVE,观察 dump.rdb 生成
  2. 开启 AOF(CONFIG SET appendonly yes),写入数据,cat appendonly.aof 看命令格式
  3. 模拟崩溃:SHUTDOWN NOSAVE 后重启 Redis,观察数据是否还在

下一章:第 4 章:Redis 集群 →

本站基于 VitePress 构建 · 由 StackHub 团队维护