一、前言
Redis 中数据存储模式有 2 种:cache-only、persistence
cache-only 即只做为“缓存”服务,不持久数据,数据在服务终止后将消失,此模式下也将不存在“数据恢复”的手段,是一种安全性低/效率高/容易扩展的方式;
persistence 即为内存中的数据持久备份到磁盘文件,在服务重启后可以恢复,此模式下数据相对安全。
对于 persistence 持久化存储,Redis 提供了两种持久化方法:
Redis DataBase(简称RDB)
Append-only file (简称AOF)
除了这两种方法,Redis 在早期的版本还存在虚拟内存的方法,现在已经被废弃。
二、RDB 概述
①、原理
RDB 是在某个时间点将数据写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
缺点:RDB 是间隔一段时间进行持久化,如果持久化之间r edis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
这里说的这个执行数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置 redis 在 n 秒内如果超过 m 个 key 被修改这执行一次 RDB 操作。这个操作就类似于在这个时间点来保存一次 Redis 的所有数据,一次快照数据。所以这个持久化方法也通常叫做 snapshots。
②、配置
RDB 默认开启,redis.conf 中的具体配置参数如下:
#dbfilename:持久化数据存储在本地的文件
dbfilename dump.rdb
#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
dir ./
##snapshot触发的时机,save##如下为900秒后,至少有一个变更操作,才会snapshot
##对于此值的设置,需要谨慎,评估系统的变更操作密集程度
##可以通过"save """来关闭snapshot功能
#save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。
save 900 1
save 300 10
save 60 10000
##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
stop-writes-on-bgsave-error yes
##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间
rdbcompression yes
snapshot 触发的时机,是有“间隔时间”和“变更次数”共同决定,同时符合 2 个条件才会触发 snapshot,否则“变更次数”会被继续累加到下一个“间隔时间”上。snapshot 过程中并不阻塞客户端请求。snapshot 首先将数据写入临时文件,当成功结束后,将临时文件重名为 dump.rdb。
③、客户端使用命令进行持久化 save 存储
自动的持久化数据存储到 dump.rdb 后。实际只要重启 redis 服务即可完成(启动 redis 的 server 时会从 dump.rdb 中先同步数据)
客户端使用命令进行持久化save存储:
redis-cli -h ip -p port save
redis-cli -h ip -p port bgsave
每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。
A、save
save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。运行 save 命令对应 Redis 日志如下:
DB saved on disk
B、bgsave
bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一段时间很短。运行 bgsave 名字对应的Redis 日志如下:
Background saving started by pid 3152
DB saved on disk
RDB: 0MB of memory userd by copy-on-write
Background saving terminated with success
bgsave 命令是针对 save 阻塞问题做的优化。因此 Redis 内部所有涉及到 RDB 操作都采用 bgsave 的方式,而 save 命令可以废弃。
bgsave 流程说明
bgsave 是主流的触发 RDB 持久化方式,下图是运作流程

1)执行 bgsave 命令,Redis 父进程判断当前是否存在正在执行的子进程,如只 RDB/AOF 子进程,如果存在 bgsave 命令直接返回。
2)父进程执行 fork 操作创建子进程,fork 操作过程中父进程会阻塞,通过 info stats 命令查看latest_fork_usec 选项,可以获取最近一个 fork 以操作的耗时,单位为微秒。
3)父进程仍 fork 完成后,bgsave 命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。
4)子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行 lastsave 命令可以获取最后一次生成成 RDB 的时间,对应 info 统计的 rdb_last_save_time 选项。
5)进程发送信号给父进程衣示完成,父进程更新统计信息,具体见 info Persistence 下的 rdb_* 相关选项。
三、AOF 概述
①、原理
Append-only file,将“操作 + 数据”以格式化指令的方式追加到操作日志文件的尾部,在 append 操作返回后(已经写入到文件或者即将写入),才进行实际的数据变更,“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。AOF 相对可靠,它和 mysql 中bin.log、apache.log、zookeeper 中 txn-log 简直异曲同工。AOF 文件内容是字符串,非常容易阅读和解析。
优点:可以保持更高的数据完整性,如果设置追加 file 的时间是 1s,如果 redis 发生故障,最多会丢失 1s 的数据;且如果日志写入不完整支持 redis-check-aof 来进行日志修复;AOF 文件没被rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall)。
缺点:AOF 文件比 RDB 文件大,且恢复速度慢。
我们可以简单的认为 AOF 就是日志文件,此文件只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为AOF 持久化模式还伴生了“AOF rewrite”。
AOF 的特性决定了它相对比较安全,如果你期望数据更少的丢失,那么可以采用 AOF 模式。如果 AOF 文件正在被写入时突然 server 失效,有可能导致文件的最后一次记录是不完整,你可以通过手工或者程序的方式去检测并修正不完整的记录,以便通过 aof 文件恢复能够正常;同时需要提醒,如果你的 redis 持久化手段中有 aof,那么在server 故障失效后再次启动前,需要检测 aof 文件的完整性。
②、配置
AOF 默认关闭,开启方法,修改配置文件 reds.conf:appendonly yes
##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes
##指定aof文件名称
appendfilename appendonly.aof
##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
appendfsync everysec
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no
##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb
##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100
AOF 是文件操作,对于变更操作比较密集的 server,那么必将造成磁盘 IO 的负荷加重;此外 linux 对文件操作采取了“延迟写入”手段,即并非每次 write 操作都会触发实际磁盘操作,而是进入了 buffer 中,当 buffer 数据达到阀值时触发实际写入(也有其他时机),这是 linux 对文件系统的优化,但是这却有可能带来隐患,如果 buffer 没有刷新到磁盘,此时物理机器失效(比如断电),那么有可能导致最后一条或者多条 aof 记录的丢失。通过上述配置文件,可以得知 redis 提供了 3 种 aof 记录同步选项:
always:每一条 aof 记录都立即同步到文件,这是最安全的方式,也以为更多的磁盘操作和阻塞延迟,是 IO 开支较大。
everysec:每秒同步一次,性能和安全都比较中庸的方式,也是 redis 推荐的方式。如果遇到物理服务器故障,有可能导致最近一秒内 aof 记录丢失(可能为部分丢失)。
no:redis 并不直接调用文件同步,而是交给操作系统来处理,操作系统可以根据 buffer 填充情况/通道空闲时间等择机触发同步;这是一种普通的文件操作方式。性能较好,在物理服务器故障时,数据丢失量会因 OS 配置有关。
其实,我们可以选择的太少,everysec 是最佳的选择。如果你非常在意每个数据都极其可靠,建议你选择一款“关系性数据库”吧。
AOF 文件会不断增大,它的大小直接影响“故障恢复”的时间,而且 AOF 文件中历史操作是可以丢弃的。AOF rewrite 操作就是“压缩”AOF 文件的过程,当然 redis 并没有采用“基于原 aof文件”来重写的方式,而是采取了类似 snapshot 的方式:基于 copy-on-write,全量遍历内存中数据,然后逐个序列到 aof 文件中。因此 AOF rewrite能够正确反应当前内存数据的状态,这正是我们所需要的;*rewrite 过程中,对于新的变更操作将仍然被写入到原 AOF 文件中,同时这些新的变更操作也会被r edis 收集起来(buffer,copy-on-write 方式下,最极端的可能是所有的 key 都在此期间被修改,将会耗费2倍内存),当内存数据被全部写入到新的 aof 文件之后,收集的新的变更操作也将会一并追加到新的 aof 文件中,此后将会重命名新的 aof 文件为 appendonly.aof,此后所有的操作都将被写入新的 aof 文件。如果在 rewrite 过程中,出现故障,将不会影响原 AOF 文件的正常工作,只有当 rewrite 完成之后才会切换文件,因为 rewrite 过程是比较可靠的。
触发 rewrite 的时机可以通过配置文件来声明,同时 redis 中可以通过 bgrewriteaof 指令人工干预。
redis-cli -h ip -p port bgrewriteaof
因为 rewrite 操作/aof 记录同步/snapshot 都消耗磁盘 IO,redis 采取了“schedule”策略:无论是“人工干预”还是系统触发,snapshot 和 rewrite 需要逐个被执行。
AOF rewrite 过程并不阻塞客户端请求。系统会开启一个子进程来完成。
当触发 AOF 重写时,内部做了那些事?下面结合图介绍它的运行流程

流程说明:
1)执行 AOF 重写请求。如果当前进程正在执行 AOF 重写,请求不执行并返回如下响应:ERR Background append only file rewriting already in progress 如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成后再执行,返回如下响应:Background append only file rewriting scheduled
2) 父进程执行 fork 创建子进程,开销等同于 bgsave 过程。
3.1) 主进程 fork 操作完成后,继续响应其他命令。所有修改命令依然写入 AOF 缓冲区并更具appendfsync 策略同步到硬盘,保证原有 AOF 机制正确性。
3.2) 由于 fork 操作运用写时复制技术,子进程只能共享 fork 操作时的内存数据。由于父进程依然响应命令,Redis 使用"AOF 重写缓冲区"保存这部分新数据,防止新 AOF 文件生成期间丢失这部分数据。
4)子进程根据内存快照,按照命令合并规则写入到新的 AOF 文件。每次批量写入硬盘数据量由配置 aof-rewrite-incremental-fsync 控制,默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞。
5.1)新 AOF 文件写入完成后,子进程发送信号给父进程,父进程更新统计信息,具体见 info persistence下的aof_*相关统计。
5.2)父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件。
5.3)使用新 AOF 文件替换老文件,完成 AOF 重写。
四、总结
AOF 和 RDB 各有优缺点,这是有它们各自的特点所决定
AOF 更加安全,可以将数据更加及时的同步到文件中,但是 AOF 需要较多的磁盘 IO 开支,AOF 文件尺寸较大,文件内容恢复数度相对较慢。
snapshot,安全性较差,它是“正常时期”数据备份以及 master-slave 数据同步的最佳手段,文件尺寸较小,恢复数度较快。
可以通过配置文件来指定它们中的一种,或者同时使用它们(不建议同时使用),或者全部禁用,在架构良好的环境中,master 通常使用 AOF,slave 使用 snapshot,主要原因是 master 需要首先确保数据完整性,它作为数据备份的第一选择;slave 提供只读服务(目前 slave 只能提供读取服务),它的主要目的就是快速响应客户端 read 请求;但是如果你的 redis 运行在网络稳定性差/物理环境糟糕情况下,建议你 master 和 slave 均采取 AOF,这个在 master 和 slave角色切换时,可以减少“人工数据备份”/“人工引导数据恢复”的时间成本;如果你的环境一切非常良好,且服务需要接收密集性的 write 操作,那么建议 master 采取 snapshot,而 slave 采用 AOF。
五、重启加载
AOF 和 RDB 文件都可以用于服务器重启时的数据恢复。如图所示,表示 Redis 持久化文件加载流程:

流程说明:
AOF 持久化开启且存在 AOF 文件时,优先加载 AOF 文件,打印如下日志:DB loaded from append only file: 5.841 seconds
AOF 关闭或者 AOF 文件不存在时,加载 RDB 文件,打印如下日志:DB loaded from disk:5.586 seconds
加载 AOF/RDB 文件城后,Redis 启动成功。
AOF/RDB 文件存在错误时,Redis 启动失败并打印错误信息
redis 第8.1章 持久化存储-详讲