redis是一个内存数据库,数据存储到内存中。而内存的数据是不持久的,要想做到持久化,就需要让redis把数据存储到硬盘上。
因此redis既要在内存上存储一份数据,还要在硬盘上存储一份数据。这样这两份数据在理论上是完全相同的(实际上可能存在一点差异,这取决于咱们怎么进行持久化)。当要插入一个数据的时候,需要同时写入内存和硬盘(实际上怎么写入硬盘有不同的策略);当查询某个数据的时候,直接从内存读取;硬盘的数据只是在redis重启的时候,用来恢复内存中的数据。
redis实现持久化的两种方式
RDB定期的把redis内存中所有数据,给拍个”照片“,生成快照文件写入硬盘中。一旦redis重启,就可以根据刚才的”快照“把内存的数据给恢复回来。
程序员通过redis客服端,执行特定命令,触发快照的生成
save
命令,执行save
时,redis会全力投入到生成“快照”中,阻塞redis其他客服端的命令。因此一般不建议使用save
命令bgsave
命令,并不会影响redis服务器处理其他客服端的请求和命令,通过多进程实现,如下图所示:bgsave
命令,父进程会判断是否存在其他正在执行的子进程,例如RDB/AOF子进程,如果存在,bgsave
命令会直接返回fork
创建子进程,fork
过程中父进程会阻塞,可以通过info status
命令查看 latest_fork_usec选项,获取最近一次fork
操作耗时,单位ms.fork是linux提供创建子进程的api,直接把父进程复制一份(pcb,文件描述符表,虚拟地址空间(内存中的数据)),作为子进程。在redis服务器中有若干变量,保存了一些键值对数据,随着fork的进行,子进程内存中也会存在和刚才父进程一样的变量。因此子进程进行“持久化”操作,相当于把父进程的内存数据给持久化了。(父进程打开一个文件,fork之后,子进程也同样使用这个文件;这就导致子进程进行持久化写入的那个文件和父进程本来要写的文件是同一个)
:::tips
fork的“写时拷贝”机制
:::
父进程进行一些修改
在bgsave
这个场景中,绝大部分内存数据是不需要改变的,因此子进程的“写时拷贝”并不会触发很多次,也就保证了整体的“拷贝时间”是可控的
bgsave
命令返回"Background saving started"信息,不再阻塞父进程,可以继续响应其他命令lastsave
命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项。在redis的配置文件redis.conf进行设置,让redis每隔多长时间,每生产多少次修改就触发.
查看redis.conf文件
上述数值可以修改配置,但是不能让rdb操作过于频繁,因为生成一次rdb快照是一个成本比较高的成本。
redis生成的rdb文件,存放在redis的工作目录中,可以在redis.conf中查看该工作目录。
:::tips
cd /etc/
vim redis.conf
:::
切换到该工作目录
:::tips
cd /var/lib/redis/
ll
:::
其中dump.rdb
即rdb机制生成的镜像文件,redis服务器默认是开启rdb的。dump.rdb
是一个二进制文件,redis将内存中的数据,以压缩的形式,保存到这个二进制文件中。
后续redis服务器重新启动,就会加载dump.rdb
文件,在加载时会进行校验,redis提供了rdb文件的检查工具redis-check-rdb
。
由上图我们发现检查工具和redis服务器在5.0版本是同一个可执行程序,可以在运行的时候加入不同的选项,从而实现不同的功能。例如,运行的时候,加入rdb文件作为命令行参数,此时就是以检查工具的方式运行,不会真的启动redis服务器。
bgsave
触发一次生成快照
redis服务器重新启动之后,会加载rdb文件的内容,恢复内存中的数据。dump.rdb
文件如下,虽然我们看不懂二进制文件,但是能看到key1,key2,key3。
bgsave
由上图可知,如果是通过正常流程重启redis服务器,此时redis服务器在退出的时候,会自动触发rdb操作;如果是异常重启(kill -9/服务器掉电)此时redis服务器来不及生成rdb,内存中尚未保存到快照中的数据,就会随着重启而丢失。
:::info
redis生成快照,不通过手动执行命令,而是自动触发有如下情况
shutdown
命令(redis正确关闭服务器的命令)也会触发bgsave
通过创建子进程,让子进程完成持久化操作。持久化会把数据写入新的文件中,然后使用新的文件替换旧的文件。由于持久化速度太快(数据量小),难以观察;而新文件替换旧文件是容易观察的。inode
编号是文件的身份标识,两次inode
编号不同,说明文件并不是同一个文件,只是内容相同。
linux文件系统的组织方式是ext4,主要将整个文件系统分成3个部分
- 超级块,存放一些管理信息
- inode区,存放inode节点,每个文件都会分配一个inode数据结构,包含了文件的各种元数据
- block区,存放文件的数据内容
bgsave
备份,并把rdb文件复制到远程机器或者文件系统中用于备灾可以遍历旧版本的redis中所有的key,把数据取出来,插入到新的redis服务器中
aof以独立日志的方式记录每次写的命令,重启时在重新执行aof文件中的命令达到恢复数据的目的。aof主要作用是解决数据持久化的实时性问题,是目前redis持久化的主流方式。
开启aof功能需要在配置文件中设置:appendonly yes
默认不开启。当开启aof后,rdb就不生效;启动以后不再读取rdb内容。aof文件名通过appendfilename
配置文件中设置。aof文件所在位置和rdb文件所在的目录一样,默认/var/lib/redis
(可配置)
向redis中添加key1,key2,key3
appendonly.aof文件记录了你的每一个操作命令,通过一些特殊符号作为分割符,来对命令的细节做出区分。aof文件的格式是文本格式,因为文本协议具备更好的兼容性,事先简单,具备可读性。
引入aof机制,既写内存,有写硬盘,速度还会很快吗?
把数据写入缓冲区,本质还是在内存中,当进程挂了或者主机掉电,缓冲区的数据会不会丢失?
随着操作的增多,aof文件体积越来越大。redis启动的时候需要读取aof文件内容,这就会影响到redis启动时间。实际上redis启动的时候,只关注最终结果,并不关心aof文件中记录的中间过程,所以aof文件中有一些内容存在冗余。如下所示:
因此redis就引入了“重写机制”,对aof文件进行整理操作,剔除其中的冗余操作,并且合并一些操作,达到给aof文件瘦身的效果。
bgrewriteaof
命令auto-aof-rewrite-min-size
和auto-aof-rewirte-percentage
参数确定自动触发时机
序号2和4的过程
aof重写和rdb生成快照文件一样,都是通过多进程的方式。通过fork创建子进程,父进程负责接收请求,子进程负责针对aof文件进行重写。子进程只需要把当前内存中的数据,获取出来,以aof的格式写入到一个新的aof文件中。因为内存中的数据状态,就相当于把aof文件整理后的结果。此处子进程写数据的过程,类似于rdb生成一个镜像快照。只不过rdb是按照二进制的方式生成的,aof重写则是按照aof要求的文本格式生成的,都是把当前内存的所有数据状态给记录到文件中。
序号3.1的过程
子进程写新aof文件的同时,父进程仍然在不停的接收客服端新的请求,父进程会把这些请求先写入aof_buf缓冲区中,再刷新到原有的aof文件中。
序号3.2,5.1, 5.2, 5.3过程
在创建子进程的一瞬间,子进程继承了当前父进程的内存状态。因此子进程中的内存数据是父进程fork之前的状态,fork之后,新的请求,对内存造成的修改,是子进程不知道的。此时,父进程准备了一个aof_rewrite_buf缓冲区,专门放fork之后的数据。子进程把aof数据写完以后,会通过信号通知一下父进程,父进程再把aof_rewrite_buf缓冲区的内容也写到新的aof文件中,到这就可以用新的aof文件替换旧的aof文件。
什么是信号?
信号是linux的神经系统,是进程间通信的一种手段。信号能表达的信息有限,并不像socket可以传输任意数据,图中子进程向父进程发送一个信号,表示“我已经干完了”。
信号包含3部分
如果在执行
bgrewriteaof
时,当前redis已经正在进行aof重写,会咋样?
此时,不会再次执行aof重写,直接返回。
如果在执行
bgrewriteaof
时,当前redis在生成rdb文件快照,会咋样?
此时aof重写操作会等待,等待rdb快照生成完毕之后,再执行aof重写
父进程fork完成后,子进程开始写入新的aof文件,随着时间推移,子进程写完新的文件后,让新的aof文件替换旧的aof文件。此时父进程仍然继续写入旧的aof文件是否有意义?
预防极端情况,例如服务器挂了,子进程的内存数据都会丢失,新的aof文件内容不完整,所以如果父进程不坚持写入旧的aof文件,重启旧无法保证数据的完整性。
当redis启动时,会根据rdb和aof文件的内容,进行数据恢复,如下图所示:
如果开启aof,以aof为主,忽略rdb文件。因为aof中包含的数据比rdb更全。
aof是按照文本的方式写入文件的,导致后续加载的成本较高。因此redis引入“混合持久化”结合了rdb和aof特点,按照aof的方式,把每个请求/操作都记录到文件中,在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件中,后续再进行的操作,仍然按照aof文本的方式追加到文件后头。
在redis.conf配置文件中开启混合持久化
示例:
打开appendonly.aof文件