Redis 的强劲性能很大程度上是由于其将所有数据都存储在了内存中,然而当 Redis 重启后,所有存储在内存中的数据就会丢失。将 Redis 作为数据库使用或者为了防止缓存被穿透后导致雪崩现象时,我们会希望 Redis 在重启后能够保证数据不丢失。这就需要 Redis 能够将数据从内存中以某种形式同步到硬盘中,使得重启后可以根据硬盘中的记录恢复数据。这一过程就是持久化。
Redis 支持两种方式的持久化,一种是 RDB 方式,另一种是 AOF 方式。
前者会根据指定的规则“定时”将内存中的数据存储在硬盘上,而后者在每次执行命令后将命令本身记录下来。
两种持久化方式可以单独使用其中一种,但更多情况下是将二者结合使用。
RDB 方式
RDB 方式的持久化是通过 快照(snapshotting) 完成的,当符合一定条件时 Redis 会自动将内存中的所有数据生成一份副本并存储在硬盘上,这个过程即为 “快照”。Redis 会在以下几种情况下对数据进行快照:
-
根据配置规则进行自动快照;
-
用户执行
save
或bgsave
命令; -
执行
flushall
命令; -
执行复制(replication)时。
根据配置规则进行自动快照
进行快照的条件可以由用户在配置文件中自定义,当符合快照条件时,Redis 会自动执行快照操作。
快照条件由两个参数构成:时间窗口 M 和 改动的键的个数 N。每当时间 M 内被更改的键的个数大于 N 时,即符合自动快照条件。
Redis 安装目录中包含的样例配置文件中预置的 3 个条件如下:
save 90 1
save 300 10
save 60 10000
每条快照条件占一行,同时可以存在多个条件,条件之间是 “或” 的关系。就这个例子而言,save 900 1
的意思是在 15 分钟(900 秒)内有一个或一个以上的键被更改则进行快照。同理,save 300 10
表示在 300 秒内至少有 10 个键被修改则进行快照。
用户执行 SAVE 或 BGSAVE 命令
除了让 Redis 自动进行快照外,当进行服务重启、手动迁移以及备份时我们也会需要进行手动执行快照操作。Redis 提供了两个命令来完成这一任务:
SAVE 命令
当执行 save
命令时,Redis 同步 地进行快照操作,在快照执行的过程中会阻塞所有来自客户端的请求。当数据库中的数据比较多时,这一过程会导致 Redis 较长时间不响应,所以应该尽量避免在生成环境中使用这一命令。
BGSAVE 命令
需要手动执行快照时推荐使用 bgsave
命令。bgsave
命令可以在后台 异步 地进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求。
执行 bgsave
命令后 Redis 会立即返回 OK 表示开始执行快照操作,如果想知道快照是否完成,可以通过 lastsave
命令获取最近一次成功执行快照的时间,返回结果是一个 Unix 时间戳。
执行自动快照时,Redis 采用的策略就是异步快照。
执行 FLUSHALL 命令
当执行 flushall
命令时,Redis 会清除数据库中的所有数据。需要注意的是,不论清空数据库的过程中是否触发了自动快照条件,只要自动快照条件不为空,Redis 就会执行一次快照操作。
当没有自定义快照条件时,执行 flushall
则不会进行快照。
执行复制时
当设置了主从模式时,Redis 会在复制初始化时进行自动快照。
使用复制操作时,即使没有定义自动快照条件,并且没有手动执行过快照操作,也会生成 RDB 文件。
快照原理
Redis 默认会将快照文件存储在 Redis 当前进程的工作目录中的 dump.rdb 文件中,可以通过配置 dir 和 dbfilename 两个参数分别指定快照文件的存储路径和文件名。快照的过程如下:
-
Redis 使用 fork 函数复制一份当前进程(父进程)的副本(子进程);
-
父进程继续接受并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件;
-
当子进程写入完所有数据后会用该临时文件替换旧的 RDB 文件,至此一次快照操作完成。
在执行 fork 的时候类 Unix 操作系统会使用写时复制(copy-on-write)策略,即 fork 函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时,操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的 RDB 文件存储的是执行 fork 一刻的内存数据。
写时复制策略也保证了在 fork 的时候虽然看上去生成了两份内存副本,但实际上内存的占用量并不会增加一倍。
另外需要注意的是,当进行快照的过程中,如果写入操作较多,造成 fork 前后数据差异较大,是会使得内存使用量显著超过实际数据大小的,因为内存中不仅保存了当前的数据库数据,而且还保存着 fork 时刻的内存数据,进行内存用量估算时很容易忽略这一点,造成内存用量超限。
Redis 在进行快照的过程中不会修改 RDB 文件,只有快照结束的时候才会将旧的文件替换成新的,也就是说 任何时候 RDB 文件都是完整的。
RDB 文件是经过压缩的(可以配置 rdbcompression
参数以禁用压缩节省 CPU 占用)的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。
通过 RDB 方式实现持久化,一旦 Redis 异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。
例如,使用 Redis 存储缓存数据时,丢失最近几秒的数据或者丢失最近更新的几十个键并不会有很大的影响。如果数据相对重要,希望将损失降到最低,则可以使用 AOF 方式进行持久化。