我们都知道,redis 是一个基于内存的数据库。基于内存的好处是访问速度快,缺点是“不持久”——当数据库重启时内存中原先的数据全都会被清空。然而小孩子才做选择,redis 说我全都要:当插入一份新的数据时,在内存中会存储一份,在磁盘中也会存储一份(当然具体怎么写 redis 有自己的策略,从而保证足够的效率)
当要访问数据时,直接从内存中获取;当 redis 重启时,可以根据磁盘中的备份来恢复。虽然会消耗更多的磁盘空间,然而磁盘资源在计算机世界里可以说是最廉价的了,这样的开销并不会带来多大的成本。
Redis官网中列出了如下四种持久化策略:
RDB(Redis Database)是 redis 中默认使用的持久化策略,它的思想是定期对 redis 进行一次全量备份,备份数据是二进制的方式存储的,其存储路径由配置文件 redis.conf 指定。
RDB持久化过程分为自动触发和手动触发,下面分别介绍
自动触发有三种情况:
在m秒内数据集发生了n次修改则自动触发。m和n的具体数值由配置文件决定,在我当前的配置文件下有3中选项
注意,m与n是并且的关系,只有同时满足才会被触发。例如上面的触发条件翻译为:距离上一次备份过去 60s 时,至少发生了 10000 次修改则触发 rdb 备份,另外两条同理。如果你想禁止自动生成 rdb 快照,你可以这样修改配置文件:save ""
从节点进行全量复制操作时,主节点自动进行RDB持久化,随后将RDB文件内容发送给从结点
执行shutdown命令关闭Redis时,执行RDB持久化。所以说正常退出 Redis 服务器前都会对当前的数据进行一次备份
触发命令:
手动出发 RDB 的指令有两个:
执行流程:
执行 bgsave
命令后,Redis 父进程会首先检查是否有正在执行 RDB/AOF 备份的子进程,如果有 bgsave 就立刻返回,间隔时间这么短,再开一个子进程备份的意义不大。
父进程执行 fork 创建子进程。fork过程中父进程可能会被阻塞,最近一次的fork操作耗时可以在redis 服务端使用 info
指令查看,单位为微秒。
父进程 fork() 完成后,就可以继续接受客户端的业务请求,而将备份的任务交给子进程完成
子进程会向一个临时的快照文件写入 redis 备份数据,当备份完成时,会删除历史的 dump.rdb 并将该快照文件重命名为 dump.rdb,从而保证了自始至终只有一份 rdb 备份文件
子进程通过信号通知父进程 rdb 备份工作完成
实验验证:
打开 /var/lib/redis (具体路径因人而异,见配置文件)下的 dump.rdb 文件,正如我们所预期的一样,文件以二进制的形式存储,所以呈现出来的都是人眼不能识别的乱码:
使用 stat
指令记录下此时的 innode 编号
执行 bgsave 手动完成 rdb 备份时,根据 Inode 编号我们就可以发现文件已经被替换了。
保存:
RDB文件保存在 dir 配置指定的目录(默认/var/lib/redis/)下,文件名通过 dbfilename配置(默认dump.rdb)指定。
可以通过执行 config set dir{newDir}
和 config set dbfilename{newFilename}
运行期间动态执行,当下次运行时RDB文件会保存到新目录
压缩:
Redis默认采用 LZF 算法对生成的 RDB 文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数 config set rdbcompression{yes|no}
动态修改
校验:
dump.rdb 里面的数据不要乱改,修改后可能看不出什么影响,也可能会导致数据错误,甚至有可能导致 redis 服务端启动失败。
现在好了,连 redis 客户端也连不上了,看看日志文件怎么说(日志文件路径同样可以在配置文件中找到,对应 logfile 配置项)
日志说的很明白了,都是 RDB 文件格式不对惹的祸。当 dump.rdb 被恶意修改时,其结果是不可预期的。对此,我们可以使用 redis 提供的 rdb 文件格式检查工具进行检查:
优点:
缺点:
前面谈到,RDB 最大的缺点是不能实时持久化保存数据,在两次快照之间,实时数据可能会因为 Redis 异常退出而丢失。而 AOF 则可以较好的处理实时性的问题。
AOF类似于mysql中的binlog,它会以独立日志的方式记录 Redis 服务端收到的每一条写入操作,重启时再重新执行AOF文件中的各种命令达到恢复数据的目的。 AOF 解决了数据的实时性问题,因此已经成为了 Redis 持久化的主流方式。
Redis 中默认采用的持久化策略为 RDB,开启 AOF 功能需要设置配置: appendonly yes
,当RDB 与 AOF 可以同时设置,但如果开启 AOF ,Redis 在启动时就会从 AOF 中加载数据
具体流程:
缓冲区:
AOF过程中为什么需要aof_buf这个缓冲区?Redis使用单线程响应命令,如果每次写AOF文件都直接同步硬盘,性能从内存的读写变成IO读写,必然会下降。先写入缓冲区可以有效减少IO次数。同时,Redis还可以提供三种缓冲区同步策略,让用户根据自己的需求做出合理的平衡 :
虽然缓冲中的数据在刷盘前也存在丢失的风险,但是相较于 RDB,AOF的刷新频率很快,所以你最多只会丢失 1s,甚至一行命令的数据
基本认识:
随着命令不断写入AOF,.aof
文件会越来越大,Redis 服务端重启的速度自然越来越慢。为了解决这个问题,Redis引入 AOF 重写机制来压缩文件体积。
重写机制就是去除冗余操作,合并相同操作的过程,虽然听起来很复杂,但实现起来却很简单,因为对于 aof 文件而言,并不需要关心中间的各种过程,只关心最后 Redis 数据库的状态,因此重写的本质是将 Redis 进程内的数据转化为写命令同步到新的 AOF文件
触发条件:
AOF 重写过程有自动触发和手动触发两种方式:
自动触发:触发时机由配置文件中的参数决定,两个同样是 “并且” 的关系
手动触发:调用 bgrewriteaof
命令
重写流程:
重写缓冲区
为什么需要重写缓冲区,看下面这个例子:
时间 | 父进程 | 子进程 |
---|---|---|
t1 | execute command “set key1 1” | |
t2 | execute command “set key2 2” | |
t3 | Create subprocess, execute AOF file rewrite | Start AOF file rewrite |
t4 | execute command “set key1 11” | execute the rewrite operation |
t5 | execute command “set key2 22” | execute the rewrite operation |
t6 | …… | Complete AOF rewrite |
我们知道,进程具有独立性,子进程创建后就和父进程在数据上独立了,这就意味着子进程只会记录上图中 t3 时刻 Redis 内存中的数据。因此父进程额外开辟了一块缓冲区用于记录fork 后父进程收到的请求,在子进程完成重写后再将这块缓冲区追加到 new AOF 文件的结尾,就可以保证数据的一致性。
AOF缓冲区
为什么 fork 后父进程还要向 AOF 缓冲区写入数据呢?对于安全问题,我们往往要考虑到各种极端的情况。如果子进程还没有完成重写 Redis 服务端就异常退出了,抑或是主机掉电了,那么新 AOF 文件的内容肯定是不完整的,内存中的 aof_rewrite_buf 也已经丢失,这就意味着 fork 后插入的数据都丢失了。
因为即使 fork 后,父进程仍然要向 aof_buf 写入,并按照刷新策略定期刷新到磁盘,从而保证数据的安全
AOF命令写入的内容直接是文本协议格式。例如set hello world这条命令,在AOF文件中会追加如下文本 :
此处遵守Redis格式协议,Redis选择文本协议可能的原因:文本协议具备较好的兼容性;实现简单;具备可读性。 对于 AOF 文件,redis 同样提供了格式检查工具 redis-check-aof,这里不再重复演示。
优点:
FLUSHALL
删除所有数据,你也可以根据 AOF 文件恢复出 FLUSHALL
之前的所有数据,在没有 rewrite 的前提下。缺点:
Redis引入了“混合持久化”的方式,结合了 AOF + RDB 的优点:
按照 AOF 的方式记录每一个请求
在触发 AOF 重写后,就把当前内存的状态按照 RDB 二进制文件的格式写入到新的 aof 文件中(对上一个文件重写后的结果)
后续再进行操作仍然是以 aof 文本的方式追加到 aof 文件末尾