Redis 提供了两种主要的持久化机制,4.0 之后还引入了 混合持久化(RDB + AOF) 的方式,结合两者优点:

  • RDB机制(Redis DataBase)(快照二进制)在指定时间间隔快照存储,将某一时刻的内存快照数据以二进制方式写入磁盘。
  • AOF机制(Append Only File)(命令追加)记录每次对服务器写操作,将所有的操作命令按顺序记录下来,追加到特殊的文件中。当服务器重启的时候会重新执行这些命令来恢复原始的数据,以redis协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
  • 混合机制: (同时开启两种持久化方式)将当前的数据以RDB方式写入文件,再将操作命令以AOF方式写入文件。 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

RDB(Redis DataBase)

RDB 持久化是指在指定的时间间隔内,将内存中的数据集快照写入磁盘。创建一个经过压缩的二进制文件(默认 dump.rdb)。

RDB 机制会生成全量rdb文件,可通过配置修改文件名和目录。

优缺点和适用场景

优点

  • 高性能:对 Redis 主进程影响极小。父进程除了 fork 子进程时有短暂阻塞外,其余时间可以继续处理请求。适合大规模数据恢复和备份。

  • 紧凑的文件:RDB 文件是压缩的二进制格式,文件体积小,非常适合用于灾难恢复、备份和不同环境之间的数据迁移。

  • 快速恢复:在数据量很大时,RDB 的恢复速度远快于 AOF。

缺点

  • 数据安全性低:RDB 是定时快照,在两次快照之间如果服务器宕机,会丢失最后一次快照之后的所有数据。

    在redis意外停止工作的情况下可能丢失数据,虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.

  • fork 可能阻塞:虽然 fork 操作本身很快,但如果数据量非常大,fork 过程可能会消耗较多 CPU,并且在内存写入压力大时,可能导致父进程短暂阻塞。在极端情况下,如果数据集巨大,fork 操作本身可能会很慢。

    RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候**,fork的过程是非常耗时**,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

适用场景

  • 适合备份,RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.

  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.

  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.

  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

快照原理

在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

img

Redis 借助了操作系统提供的写时复制技术(Copy-On-Write, COW),可以让在执行快照的同时,正常处理写操作。

Linux中fork()是使用写时复制的页实现的,所以它唯一的代价是:复制父进程的页表以及为子进程创建任务结构所需的时间和内存。

具体流程

  1. 触发机制:当满足配置条件(如 save 900 1 表示900秒内至少1个键变化)或手动执行 SAVE/BGSAVE 命令时,持久化开始。

  2. 父进程 fork 子进程:Redis 主进程(父进程)会 fork 一个子进程。这个子进程会拥有与父进程完全相同的内存数据副本。

  3. 子进程负责写入:子进程将内存中的数据写入一个临时的 RDB 文件。由于是副本,子进程的写入操作不会影响父进程继续处理客户端的命令。

  4. 替换旧文件:当子进程完成对新 RDB 文件的写入后,它会用新文件原子地替换旧的 RDB 文件。

简单来说就是 fork()函数会复制父进程的地址空间到子进程中,复制的是指针,而不是数据,所以速度很快。

当没有发生写的时候,子进程和父进程指向地址是一样的,父子进程共享内存空间。直到发生写的时候,系统才会真正拷贝出一块新的内存区域,读操作和写操作在不同的内存空间,子进程所见到的最初资源仍然保持不变,从而实现父子进程隔离。

此做法的主要优点是如果期间没有写操作,就不会有副本被创建。

Redis 可以高效地执行 RDB 持久化操作,并且不会对 Redis 运行过程中的性能造成太大的影响。同时,这种方式也提供了一种简单有效的机制来保护 Redis 数据的一致性和可靠性。

Redis写时复制

写时复制

写时复制 (Copy-On-Write, COW)
在 fork 子进程的瞬间,父子进程共享同一片内存数据。当父进程要修改某一片数据时(比如执行了一个 SET 命令),操作系统会将被修改的内存页复制一份,确保子进程的数据不受影响。这使得 fork 操作非常快速,并且大部分情况下内存消耗不会翻倍。

拓展:JDK的 CopyOnWriteArrayList CopyOnWriteArraySet 容器也使用到了写时复制技术。

主动触发

**主动方式:**即手动触发,涉及两个操作命令:

  • SAVE:阻塞 Redis 直到 RDB 完成(不推荐生产使用)。

  • BGSAVE:利用COW技术,创建子进程,由子进程负责RDB过程,主进程可以继续处理其他命令,后台异步执行(推荐)。

img

自动触发

自动方式:由配置文件来完成,自动触发 BGSAVE命令

在redis.conf配置文件中有如下配置:

1
2
3
save 900 1     # 900秒内至少有1个key发生变化
save 300 10 # 300秒内至少有10个key发生变化
save 60 10000 # 60秒内至少有10000个key发生变化

其他触发

  • 执行 FLUSHALL(会生成空 RDB 文件)

  • 主从复制时,主节点自动执行 BGSAVE

  • 关闭 Redis 时(若配置了 save

RDB 阻塞

redis提供了两个命令来生成RDB文件,分别是savebgsave

  • save:在主线程中执行,会导致阻塞。

  • bgsave:创建一个子进程,专门用于写入RDB文件,避免了主线程的阻塞,这也是Redis RDB文件生成的默认配置。

子进程需要通过fork操作,从主线程中创建出来。子进程在创建后不会再阻塞主线程,但是,fork这个创建过程本身会阻塞主线程,而且主线程内存越大,阻塞时间越长。

RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。

持久化配置

Redis会将数据集的快照dump到dump.rdb文件中。

通过配置文件来修改Redis服务器dump快照的频率和位置:

1
2
3
4
5
6
7
save 900 1       #在900秒(15分钟)内,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)内,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)内,如果至少有10000个key发生变化,则dump内存快照。

dbfilename dump.rdb # 快照文件名
dir ./ # 快照文件存放目录
rdbcompression yes # 是否压缩

AOF(Append Only File)

AOF 持久化是以日志的形式记录每一个写操作命令。当 Redis 重启时,会重新执行 AOF 文件中的所有命令来重建内存中的数据。

A每一个写、删除操作,查询操作不会记录,以文本的方式追加记录

img

优缺点和适用场景

优点

  • 数据安全高:根据 appendfsync 策略,可以提供非常好的持久化保证,最多丢失一秒数据。

    使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据

  • 易于理解和解析:AOF 文件是纯文本协议格式,易于阅读和手动修复(不推荐)。

  • 容灾性好:即使 AOF 文件末尾有损坏(比如突然断电),可以使用 redis-check-aof 工具轻松修复。

缺点

  • 文件体积大:通常 AOF 文件会比同数据集的 RDB 文件大。

  • 恢复速度慢:在数据集很大时,重新执行所有 AOF 命令来恢复数据会比加载 RDB 文件慢。

    在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

  • 对性能影响相对较大:虽然 everysec 策略性能不错,但在写入负载极高的场景下,AOF 仍可能成为瓶颈。

适用场景

  • 占用内存少,AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.

  • 重写压缩文件,Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。

  • **容易阅读,**AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

追加原理

AOF 的工作流程可以概括为:命令追加、文件写入、文件同步

  1. 命令记录:修改和新增命令执行后,都会以 Redis 协议格式追加到服务器内部的 AOF 缓冲区

  2. 写入与同步策略:根据配置的 appendfsync 策略,决定何时将缓冲区的内容写入并同步到 AOF 磁盘文件。

    • always:每次写命令都立即同步到磁盘。数据最安全,不会丢失任何命令,但性能最差
    • everysec:每秒同步一次。这是默认推荐的策略,是一种折中方案。在性能和数据安全之间取得了很好的平衡,最多丢失1秒钟的数据。
    • no:由操作系统决定何时同步。性能最好,但数据丢失风险最高。

重写机制

随着命令不断写入,AOF 文件会越来越大。为了解决这个问题,Redis 提供了 AOF 重写 机制。

  • 目的:创建一个新的、更小的 AOF 文件,这个文件包含了恢复当前数据集所需的最小命令集合。例如,对一个键进行了100次 INCR,重写后只需要一条 SET 命令。

  • 过程:与 RDB 类似,也是 fork 一个子进程 在后台完成。子进程遍历数据库,将每个键值对用一条命令记录到新的 AOF 文件中。在重写期间,新的写命令会被同时记录到原有的 AOF 缓冲区重写 AOF 缓冲区。当子进程完成重写后,会通知父进程,父进程会将重写 AOF 缓冲区的内容追加到新的 AOF 文件中,然后原子地替换旧的 AOF 文件。

重写缓冲区

子进程在AOF重写期间,父进程还是在继续接收和处理命令。

  • 子进程:拥有 fork 创建时刻的内存数据副本,它基于这个"过去"的状态来生成新的 AOF 文件

  • 父进程:继续正常服务,接收并执行新的写命令,数据库状态在不断变化

新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。

为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区(aof_rewrite_buf_blocks)。

AOF重写缓存区在AOF重写时开始启用。

执行步骤

img
  1. 准备阶段,当开始执行 BGREWRITEAOF 或满足自动重写条件时:

    • 父进程 fork 出子进程
    • 父进程会同时开启 AOF 重写缓冲区
  2. 重写期间的写命令处理

    从这一刻开始,所有到达的写命令会同时进入两个缓冲区:

    缓冲区 用途 消费者
    常规 AOF 缓冲区 用于正常的 AOF 持久化 AOF 文件(根据 appendfsync 策略)
    AOF 重写缓冲区 专门用于保证重写一致性 新的 AOF 文件(在重写完成后)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def handle_write_command(command):
    # 1. 执行命令,更新内存数据库
    execute_command(command)

    # 2. 将命令追加到常规 AOF 缓冲区
    aof_buf.append(command)

    # 3. 同时追加到 AOF 重写缓冲区
    aof_rewrite_buf.append(command)
  3. 子进程完成重写

    1. 子进程向父进程发送信号
    2. 父进程收到信号后,将 AOF 重写缓冲区中的所有命令 追加到子进程生成的新 AOF 文件中
    3. 原子性地用新的 AOF 文件替换旧的 AOF 文件,这样重写后数据库状态就和服务器当前的数据库状态一致

AOF重写缓存区同步至AOF文件中,这个过程是同步的,会阻塞父进程,在其他时候,AOF后台重写都不会阻塞父进程

AOF 阻塞

AOF日志是在主线程中执行的,采用先存数据再写日志的方式,可以避免对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为如果把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而阻塞后续操作。

  • Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;

  • Everysec,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区内容写入磁盘。

  • No,操作系统控制写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

持久化配置

在Redis的配置文件中存在三种同步方式,通过 appendfsync 配置控制写入磁盘的频率::

1
2
3
4
5
6
# 每个命令都同步(最安全,性能最差)
appendfsync always
# 每秒同步一次(默认,平衡安全与性能)
appendfsync everysec
# 由操作系统决定(性能最好,可能丢失较多数据)
appendfsync no

混合持久化(RDB + AOF)

为了解决 AOF 恢复慢的问题,同时保证数据安全,Redis 4.0 引入了混合持久化。

当开启混合持久化(aof-use-rdb-preamble yes)后,AOF 重写过程会发生变化,AOF 重写时不再使用纯命令日志,而是先写入 RDB 格式的全量数据,再追加重写期间的增量命令:

  1. 子进程进行重写时,不再是直接将数据以命令形式写入新 AOF 文件。

  2. 而是会先将当前内存中的数据做一次 RDB 快照,并写入新 AOF 文件的开头

  3. 然后,在重写期间接收到的写命令,会以 AOF 格式(Redis 协议)追加到 RDB 数据之后

“先拍一张快照,然后记录之后的指令”

最终生成的新的 AOF 文件是一个“RDB头部 + AOF尾部”的混合体。半部分是 RDB 二进制格式,后半部分是 AOF 命令文本。

  • 结合了 RDB 和 AOF 的优点

    • 快速恢复:重启时,先加载 RDB 部分的内容,速度非常快,再重放少量 AOF 命令(精确)。文件大小比纯 AOF 小,恢复速度比纯 AOF 快。
    • 数据安全:再重放尾部 AOF 格式的增量命令,保证数据不丢失。
1
2
3
4
5
6
7
8
9
10
11
12
13
# 开启 AOF
appendonly yes

# 使用混合持久化
aof-use-rdb-preamble yes

# AOF 同步策略使用每秒一次
appendfsync everysec

# 可选的 RDB 备份,作为多一重保障
save 900 1
save 300 10
save 60 10000

比较和建议

如果两个都配了优先加载AOF

  • RDB 像是一次全量拍照,速度快,文件小,但可能会丢数据。

  • AOF 像是记录所有操作指令的日志,数据安全,但文件大,恢复慢。

  • AOFRDB更安全也更大

  • RDB性能比AOF

特性 RDB AOF 混合持久化 (推荐)
数据安全 低,可能丢失分钟级数据 高,最多丢失秒级数据 高,同 AOF
文件大小 小,压缩二进制 大,文本协议 比纯 AOF 小
恢复速度 非常快 (先RDB,后增量AOF)
对性能影响 fork 时可能阻塞,写入压力小 写入压力取决于 appendfsync 同 AOF
适用场景 容灾备份,允许分钟级数据丢失 对数据安全要求高,如金融业务 绝大多数生产环境

如何选择持久化策略?

场景 推荐方案
要求高性能,可容忍少量数据丢失 仅 RDB
要求高数据安全性,不能容忍丢失 AOF(everysecalways
既要快速恢复,又要高安全性 混合持久化(推荐)
仅用作缓存,无需持久化 关闭 RDB 和 AOF