参考资料:
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
目录
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上,以防止突然宕机造成的数据丢失。
在Redis的官网中,明确说明的持久化方案有2种(传送门):
RDB: 即快照模式,将内存中某一时刻的所有数据都写入到硬盘中。
AOF :追加文件模式,存储的是Redis中所执行的命令。
这两种模式在以前只能单独使用,而4.0版本后可以混合使用。
RDB 就是 Redis DataBase 的缩写,即快照方式,RDB持久化是指将某个时间点的所有 Redis 数据保存到一个经过压缩的二进制文件(RDB 文件)中。
创建 RDB 后,用户可以对 RDB 进行备份,可以将 RDB 复制到其他服务器从而创建具有相同数据的服务器副本,还可以在重启服务器时使用。
RDB 既可以手动执行,也可以根据服务器配置选项定期执行。该功能可以将某个时间点的数据库状态保存到一个 RDB 文件中。
RDB既可以手动触发也可以自动触发。
Redis 提供save和bgsave这两个命令用于生成 RDB 文件。
bgsave流程总结如下:
1、redis客户端执行bgsave命令或者自动触发bgsave命令。
2、主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回。
3、如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作。
4、子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件。
5、同时发送信号给主进程,通知主进程rdb持久化完成。
上文中第2点提到,“主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回”,这是因为 BGSAVE命令执行期间,SAVE、BGSAVE、BGREWRITEAOF 这三个命令会与当前的 BGSAVE 操作产生竞态条件,降低性能,因此会被拒绝。
另外注意,快照文件只有1个,每次进行RBD持久化都会先写入一个临时文件,在写入完成后再替换旧的快照文件。当然,如果想保存多份快照文件,我们可以创建一个定期任务(cron job),每小时将一个 RDB 文件备份到一个文件夹,以此来实现我们的目的。
自动触发的几种方式如下:
这里我们只介绍save配置的方式,在 redis.conf中进行如下配置,当满足如下条件之一时,就会自动执行bgsave。
- save 900 1 -- 900 秒内,至少对数据库进行了 1 次修改
- save 300 10 -- 300 秒内,至少对数据库进行了 10 次修改
- save 60 10000 -- 60 秒内,至少对数据库进行了 10000 次修改
- # 文件名称
- dbfilename dump.rdb
-
- # 文件保存路径
- dir /home/work/app/redis/data/
-
- # 如果持久化出错,主进程是否停止写入
- stop-writes-on-bgsave-error yes
-
- # 是否压缩
- rdbcompression yes
-
dbfilename:RDB文件在磁盘上的名称。
dir:RDB文件的存储路径。默认设置为“./”,也就是Redis服务的主目录。
stop-writes-on-bgsave-error:当配置为yes时,如果快照操作出现异常(例如操作系统用户权限不够、磁盘空间写满等等)时,Redis就会禁止写操作。这个特性的主要目的是使运维人员在第一时间就发现Redis的运行错误,并进行解决。
rdbcompression:该属性将在字符串类型的数据被快照到磁盘文件时,启用LZF压缩算法。Redis官方的建议是请保持该选项设置为yes,因为“it’s almost always a win”。
经过前文介绍我们了解到的主线程只有在fork子进程时才会阻塞,所以在持久化期间依然会提供服务,这就产生了一个问题,RBD持久化要将整个Redis中的数据都拷贝一份进行保存,这个操作必然不是短时间内能够完成的,如果在这个过程中出现了数据的修改,该如何保证数据的一致性?
当bgsave子进程执行时,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本,主线程的修改就会发生在这个副本上,而原内存中的值不变。然后,bgsave 子进程则会把这个副本数据写入 RDB 文件,同时快照写完后这个副本内的数据还会再同步回原来的内存块中,以此来保证内存与RBD快照中的数据一致性。

AOF是以 文本日志形式 将 所有写命令以 Redis 命令请求协议格式追加到 AOF 文件的末尾,以此来记录数据的变化。当服务器重启时,会重新载入和执行 AOF 文件中的命令,就可以恢复原始的数据。
需要注意的是,AOF先写内存,后写日志。这么有2个好处:
但这种方式存在潜在风险,如果命令执行完成,但在写日志之前宕机了,会丢失数据。
AOF记录的是Redis中执行的命令,AOF默认是不开启的,要开启的话需要在redis.conf中配置appendonly yes。AOF创建的过程被分为了2步,Redis 命令请求会先保存到内存中的 AOF 缓冲区,再定期写入并同步到 AOF 文件。这两步的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
appendfsync 提供了三种写回策略:

- # appendonly参数开启AOF持久化
- appendonly no
-
- # AOF持久化的文件名,默认是appendonly.aof
- appendfilename "appendonly.aof"
-
- # AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
- dir ./
-
- # 同步策略
- # appendfsync always
- appendfsync everysec
- # appendfsync no
-
- # aof重写期间是否同步
- no-appendfsync-on-rewrite no
-
- # 重写触发配置
- auto-aof-rewrite-percentage 100
- auto-aof-rewrite-min-size 64mb
-
appendfsync:AOF写回与同步的策略
no-appendfsync-on-rewrite:AOF 重写时不支持追加命令
auto-aof-rewrite-percentage:AOF 重写百分比
auto-aof-rewrite-min-size:AOF 重写文件的最小大小
在上文介绍RDB的内容中,有介绍过当快照创建过程中数据发生修改时的处理方案,同样的问题AOF也会遇到,这里AOF与RBD的处理方法类似,但也有些区别,下面我们简单解释下。
当fork子进程时,子进程时会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存。
随着 Redis 不断运行,AOF 的体积也会不断增长,这将导致两个问题:
为了解决 AOF 体积膨胀问题,Redis 提供了 AOF 重写功能,来对 AOF 文件进行压缩。
AOF文件存储的是Redis中所执行过的指令,就比如一个列表经过不断的添加、删除后会产生n条操作记录。但是我们最后恢复数据的时候,必然只要恢复数据的最后状态就可以了,无需关注一个数据过往的状态,所以AOF重写实际上就是合并这些冗余的指令,将他们合并为一条指令,通过这条指令就可以直接获得Redis中数据最后的状态。这样做既可以实现数据的备份,又可以实现AOF文件的压缩。

首先,我们在redis.conf中配置auto-aof-rewrite-percentage、auto-aof-rewrite-min-size来设定AOF重写的时机,例如auto-aof-rewrite-percentage设为100,则表示 AOF文件的体积比上一次重写后的体积大了至少 100% 时则重写,auto-aof-rewrite-min-size则表示当AOF文件多大时开始重写(单位为M)。
在重写时,Redis会fork出一个bgrewriteaof子进程(和bgsave一样,这个操作会造成主线程的阻塞),并把主线程的内存拷贝一份给这个bgrewriteaof子进程。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。在重写完成后,再将原来的AOF文件给替换掉。
当然这里有一个问题就是数据的追加,当我们在重写日志的时候Redis并没有阻塞,因此还是会继续接收修改操作,那么这个操作如何追加到正在重写的AOF日志中呢?这里Redis会为重写进程创建一个AOF重写缓冲区,当有操作产生时,会同时记录到AOF缓冲区与AOF重写缓冲区,等重写日志完成后,再将AOF重写缓冲区中的数据追加进去即可。

RBD保存的是内存的快照,且使用了LZF压缩算法,因此文件大小不会超过内存的大小,而AOF文件因为会一直追加操作指令,因此会比较大。
RBD存储的即为内存中的数据,而AOF存储的是指令,因此使用RBD恢复数据会比AOF更快,AOF文件需要通过指令一条条改动记录,可能出现一个列表多次改动才会变为最终状态。
RBD文件使用二进制流的方式存储,不具备可读性,AOF文件了解其结构的情况下可以手动进行数据修复。
RDB方式必然产生fork,这种重量级操作会导致主线程的阻塞,且内存复制需要时间,因此无法实现秒级的持久化,而AOF可以。
由上一节我们可以看出,RBD与AOF各有优劣,于是4.0之后允许我们将他们混用。其方式为RBD以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。

Redis重启时会自动读取持久化文件,过程如下: