Skip to content

Redis 持久化机制

Redis 持久化机制是指将内存中的数据保存到磁盘,以保证数据在服务重启或故障后不会丢失。

Redis有两种持久化机制:RDB和AOF。

1.RDB

1.1 概述与工作原理

RDB全称Redis Database,是指如果在指定的时间间隔后,达到指定的修改次数后,会生成数据快照(Snapshot),并保存到磁盘的dump.rdb文件中。

RDB的工作原理如下:

  1. Redis 执行fork()创建子进程,由子进程负责持久化工作,父进程接收请求执行命令;
  2. 子进程将内存数据写入到临时RDB文件中;
  3. 当子进程完成写入工作后,替换旧的RDB文件;

由于写时复制技术,父子进程在 fork 后共享内存页。只有当父进程需要修改某个内存页时,才会复制该页,从而避免在快照期间复制所有内存,减少内存消耗。

关于fork()

fork() 方法(或 fork() 系统调用)是类 Unix 操作系统(包括 Linux、macOS 等)中一个非常核心且强大的操作,它的作用是 创建一个新的进程,它是当前进程的精确副本

这个新的进程被称为 子进程(Child Process),而调用 fork() 的原始进程则被称为 父进程(Parent Process)

当父进程调用 fork() 后,操作系统会创建一个几乎与父进程完全相同的子进程。

这里的“相同”意味着子进程会继承父进程的大部分属性,包括:

  • 代码段(Text Segment):子进程和父进程共享相同的代码。
  • 数据段(Data Segment):子进程获得父进程数据段的副本。通常采用 写时复制(Copy-on-Write, COW) 机制。这意味着最初父子进程共享物理内存页,只有当任一方尝试修改这些数据时,操作系统才会为修改方创建一份私有的副本。这大大提高了 fork() 的效率。
  • 栈(Stack):子进程获得父进程栈的副本。
  • 环境变量:子进程继承父进程的环境变量。
  • 打开的文件描述符:子进程继承父进程所有打开的文件描述符(例如标准输入、标准输出、错误输出,以及其他已打开的文件)。这意味着父进程打开的任何文件,子进程也都可以直接读写。
  • 当前工作目录:子进程继承父进程的当前工作目录。
  • 信号处理设置:子进程继承父进程的信号处理设置。

fork() 系统调用有一个非常特殊的返回值:

  • 在父进程中: fork() 返回 子进程的 PID
  • 在子进程中: fork() 返回 0
  • 如果创建失败: fork() 返回 -1,并且设置 errno 来指示错误原因。

1.2 RDB 核心配置

在Redis配置文件中,关于RDB的核心配置如下:

  • save <seconds> <changes> [<seconds> <changes> ...]:表示自上次持久化,经过<seconds>秒后,在这段时间内,修改数据库的次数达到<changes>后,就会再次进行持久化,生成数据快照并保存到磁盘中。例如:

    txt
    save 3600 1 300 100 60 10000
    1. 3600秒内(1小时),有1次变化就进行持久化;
    2. 300秒内(5分钟),有100次变化就进行持久化;
    3. 60秒内(1分钟),有10000次变化就进行持久化;

    如果要禁用RDB持久化,只需要在save后写上空字符串,如下:

    txt
    save ""
  • dir ./:配置dump.rdb文件保存的目录;

  • dbfilename dump.rdb:配置持久化文件的名称,默认就是dump.rdb

1.3 触发RDB快照

在Rrdis中,触发RDB快照的方式如下:

  • 当启用了RDB,并且达到了持久化条件,就会自动进行持久化,生成数据快照;
  • 当执行flushdbflushall命令时,会立即进行持久化,生成数据快照,但是此时某个数据库或全部数据库是空的,没有意义;
  • 当执行shutdown命令时,会立即进行持久化,生成数据快照;
  • 通过手动发出命令触发RDB进行持久化:
    • save:会阻塞 Redis 服务器进程,直到 RDB 文件完全创建完成。在此期间,Redis 不能处理任何其他命令请求,客户端发送的所有命令都会被拒绝。在生产环境中禁用!
    • bgsave:会派生(fork)一个子进程来执行 RDB 快照的创建。父进程(Redis 主进程)会继续处理客户端请求,不会被阻塞。推荐在生产环境手动触发快照时使用。

1.4 RDB优缺点

RDB的优点如下:

  • RDB 是 Redis 数据库在某一时间点的非常紧凑的单一文件表示,非常适合用于备份
  • RDB 对于灾难恢复非常出色,因为它是一个紧凑的单一文件,可以轻松地传输到远距离的数据中心,或上传到 Amazon S3(可以加密);
  • RDB 能够最大限度地提升 Redis 的性能,因为 Redis 父进程在持久化数据时唯一需要做的工作就是派生(fork)一个子进程来完成所有剩余任务,父进程本身永远不会执行磁盘 I/O 或类似操作;
  • 与 AOF(Append Only File)相比,RDB 允许在处理大型数据集时实现更快的重启

RDB的缺点如下:

  • 可能会有数据丢失的危险:RDB文件是在设定的时间点达到修改次数后才会自动生成,如果在这段时间内发生了断电、服务器崩溃等问题,会丢失最近几分钟的数据;
  • RDB 需要频繁地 fork() 来通过子进程将数据持久化到磁盘。如果数据集很大,fork() 操作可能会非常耗时,并可能导致 Redis 停止服务客户端几毫秒,甚至数据集非常大且 CPU 性能不佳时可能停止一秒钟。AOF(Append Only File)也需要 fork(),但频率较低,并且可以调整重写日志的频率,而不会在持久性方面做出任何权衡。

2. AOF

2.1 概述与工作原理

AOF,全称Append Only File,当开启了AOF配置后,Redis接收到写指令后(例如,set命令),会将其追加到AOF文件,当Redis重启时,会重新执行AOF文件中的指令来恢复数据。

当 Redis 服务器执行了任何会修改数据集的命令(例如 SETLPUSHSADDDEL 等)时,这些命令会以 Redis 命令协议的格式(RESP 协议)被追加到 AOF 缓冲区(aof_buf)中。命令不是立即写入磁盘,而是先进入缓冲区。之后,缓冲区中的命令会根据文件同步策略,调用fsync()写入磁盘中。文件同步策略如下:

  • no:不主动进行同步,数据何时写入磁盘取决于操作系统。这种模式下,数据安全性最低,但性能最好。如果服务器宕机,可能会丢失几秒钟的数据。
  • always:每个命令都会立即同步到磁盘。数据安全性最高,但性能最低,因为每次写入都会进行一次 fsync 调用。
  • everysec(默认且推荐) 每秒同步一次。这在性能和数据安全性之间取得了很好的平衡。即使服务器宕机,最多也只会丢失 1 秒的数据。

2.2 AOF核心配置

有关AOF的核心配置如下:

  • appendonly:是否开启AOF,默认值为no表示关闭,可设置如下开启AOF:

    txt
    appendonly yes
  • appenddirname:AOF文件保存目录;

  • appendfilename:AOF文件前缀名,自Redis 7之后,AOF文件包括三类:

    • base 文件(基础文件):表示某个时间点的数据库快照,base文件格式有可能是RDB(二进制序列化)或AOF(文本命令)的。只有一个。
    • increment 文件(增量文件):记录自base文件创建后,Redis接收到的写命令。可能会有多个。
    • manifest 文件(清单文件):记录AOF文件包括哪些,以及increment文件的顺序。

    例如,下面就是一个AOF文件的例子:

    txt
    appendonly.aof.1.base.rdb
    appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof
    appendonly.aof.manifest
  • appendfsync:缓冲区同步策略,即决定缓冲区的内容何时写入磁盘,配置如下:

    txt
    # appendfsync always
    appendfsync everysec
    # appendfsync no
  • aof-use-rdb-preamble:AOF文件中的base文件格式可能是RDB或AOF,该项配置决定base文件使用RDB格式,默认值为yes表示开启。

    txt
    aof-use-rdb-preamble yes

2.3 AOF重写

随着时间的推移,AOF 文件会变得越来越大。因为 AOF 记录了所有的写命令,包括许多过期、删除或者被后续操作覆盖的命令。例如,SET key value1 后又 SET key value2,那么 value1SET 命令在 AOF 中就变成了冗余。

AOF 重写机制就是为了解决这个问题,它会创建一个新的 AOF 文件,这个新文件只包含恢复当前数据集所需的最少命令序列。

AOF 重写可以自动触发,也可以手动触发。

自动触发:Redis会在满足以下两个条件时自动触发AOF重写

  • 当前 AOF 文件(increment文件)大小与上次重写后的 AOF 文件大小之比达到设定的百分比。
  • 当前 AOF 文件(increment文件)大小达到设定的最小尺寸。

上面两个参数可以配置如下:

txt
auto-aof-rewrite-percentage 100 # 当前 AOF 文件比上次重写后大 100% (即达到两倍)
auto-aof-rewrite-min-size 64mb # AOF 文件至少达到 64MB 时才触发重写

手动触发:通过 BGREWRITEAOF 命令手动触发 AOF 重写。

在Redis 7及之后,重写的工作流程如下:

  • Redis调用fork()创建子进程;
  • 子进程在临时文件中写新的基础文件(base file);
  • 父进程创建一个新的增量文件(increment file)来记录写指令;
  • 当子进程完成后(写新的基础文件),父进程获得信号,使用新的基础文件(子进程创建)和新的增量文件(父进程创建)来创建临时清单文件(manifest file);
  • Redis使用原子操作交换清单文件(新的和旧的),保证AOF重写生效;之后会清除没用的基础文件和增量文件;

2.4 AOF优缺点

AOF的优点如下:

  • 使用 AOF(Append Only File)能让 Redis 拥有更高的数据持久性

    可以配置不同的 fsync 策略:完全不进行 fsync、每秒进行一次 fsync,或者每个查询都进行 fsync。在默认的“每秒 fsync 一次”策略下,写入性能依然非常出色。fsync 操作由一个后台线程执行,主线程会尽量在没有 fsync 操作进行时执行写入,因此最多只会丢失一秒钟的写入数据。

  • AOF 日志是一个**只追加(append-only)**的日志,所以没有寻道操作,即使发生断电也不会出现数据损坏问题。即使日志因为某些原因(比如磁盘已满或其他原因)以一个不完整的命令结尾,redis-check-aof 工具也能轻松修复它。

  • 当 AOF 文件变得过大时,Redis 能够在后台自动重写 AOF 文件。重写过程是完全安全的。

  • AOF 包含了一个日志,其中所有操作都以易于理解和解析的格式一个接一个地记录。甚至可以轻松地导出 AOF 文件。例如,即使不小心使用了 FLUSHALL 命令清空了所有数据,只要在此期间没有进行日志重写,你仍然可以通过停止服务器、删除日志中的最新命令,然后重新启动 Redis 来挽救数据集。

AOF的缺点如下:

  • 对于相同的数据集而言,AOF文件比RDB文件大,因此数据恢复耗时比RDB长。

3. RDB与AOF

在Redis中,支持同时开启RDB与AOF,并且也建议同时开启。

当两者同时开启时,Redis 重启时会优先使用 AOF 文件进行数据恢复,因为 AOF 包含了更完整和最新的数据。只有当 AOF 文件丢失或损坏无法恢复时,Redis 才会尝试使用 RDB 文件进行恢复。

同时开启的好处:

  • 最大化数据安全性: AOF 确保了最少的数据丢失(例如,最多 1 秒),因为它持续记录最新的操作。

  • 快速灾难恢复: 如果需要从零开始恢复一个庞大的数据集,使用 RDB 文件通常比回放一个巨大的 AOF 文件快得多。这在紧急情况下可以显著缩短服务恢复时间。

  • 应对不同故障场景:

    • 如果 AOF 文件由于某种罕见原因损坏,RDB 可以作为最后的防线。

    • 如果 Redis 意外崩溃,AOF 确保了最新的数据。

  • 互补优势: RDB 适合做定期全量备份和异地存储;AOF 则提供了实时的数据记录和快速恢复最新状态的能力。

参考资料

[1] Redis官网:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/

[2] 尚硅谷视频教程:https://www.bilibili.com/video/BV13R4y1v7sP

[3] Docker Redis:https://hub.docker.com/_/redis

附录

Docker启动Redis

在Redis官方镜像中,是没有配置文件的,需要我们在宿主机准备配置文件目录和数据目录,并挂载到容器上:

完整启动命令如下:

bash
docker run 
-v /docker/redis/config:/usr/local/etc/redis 
-v /docker/redis/data:/data 
-p 6379:6379
-d redis:7.4.4 redis-server /usr/local/etc/redis/redis.conf

当启动完成后,会发现使用Navicat连接不上Redis,原因是Redis 默认为了安全考虑,有一些配置可能会阻止外部连接。可以修改配置文件如下配置:

  • bind 配置:如果看到 bind 127.0.0.1,这表示 Redis 只允许来自容器内部的连接,需要修改 redis.conf,将其注释掉(# bind 127.0.0.1)或者改为 bind 0.0.0.0,让 Redis 监听所有网络接口。
  • protected-mode 配置:如果看到 protected-mode yes 并且没有设置密码,这表示 Redis 拒绝来自非 localhost 的连接,除非客户端提供了密码。对于测试环境,可以将其改为 protected-mode no

修改配置后,重新启动容器,就可以成功连上Redis了。