Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面去,避免数据意外丢失。
RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中
RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。
RDB文件是保存在硬盘里面的,所以即使Redis服务器进程退出,甚至运行Redis服务器的计算机停机,但只要RDB文件仍然存在,Redis服务器就可以用他来还原数据库状态。
RDB文件的创建与载入
创建(SAVE与BGSAVE)
有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE。
SAVE命令会阻塞Redis服务器的进程,知道RDB文件创建完毕,在服务器进程阻塞期间,服务器不能处理任何命令请求的
//执行save命令,RDB进行持久化生成RDB文件
save
和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求。
这里涉及到两个问题?为什么是进程,而不是开线程(消耗更少资源,一个进程包含多个线程)
这是因为Redis是单线程的,不可能开多一个线程。
Redis为什么是单线程?
Redis是一个内存型数据库,数据都是从内存上读取的,所以CPU并不是他的上限瓶颈,因为CPU读内存是很快的,足够应付大流量,Redis的上限瓶颈是内存的大小,所以只需要一个单线程就足够了,不需要开多个线程去访问内存加快速度。
所以BGSAVE是派生出一个子进程去执行创建RDB文件。

创建RDB文件的实际工作是由rdb.c/rdbSave函数完成,SAVE命令和BGSAVE命令会以不同的方式去调用这个函数。下面用伪代码去表示两者不同
def SAVE():
//创建RDB文件
rdbSave()
End SAVE;
def BGSAVE():
//创建子进程
pid = fork()
if pid == 0:
//成功创建到子进程
//让子进程去创建RDB文件
rdbSave()
//完成之后向父进程发送信号
signal_parent()
elif pid > 0:
//子进程数量太多,轮询等待子进程创建成功
//父进程继续处理命令请求,并通过轮询等待子进程的信号
handle_requeset_and_wait_signal()
else:
//子进程为负数
//处理出错情况
handle_fork_error()
End BGSAVE;
载入RDB文件
RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时,只要检测到RDB文件存在,就会自动载入RDB文件。
另外值得一提的是,因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:
- 如果服务器开启了AOF持久化功能,那么服务器会优先用AOF文件来还原数据库的状态
- 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态

SAVE命令执行时的服务器状态
当SAVE命令在执行时,Redis服务器会被阻塞,当SAVE命令正在进行时,客户端发送的所有命令请求都会被阻塞,直到SAVE命令执行完成,重新开始接收命令请求之后,客户端发送的命令才会被处理
BGSAVE命令执行的服务器状态
BGSAVE命令是由子进程去执行的,所以在子进程创建RDB文件的过程,并不会阻塞Redis,Redis服务器仍然会继续处理客户端的命令请求,但是,在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令会和平时不一样。
首先,Redis在BGSAVE命令期间,客户端发送的SAVE命令会被服务器拒绝,服务器会禁止SAVE命令和BGSAVE命令同时执行,这种操作是为了避免父进程和子进程同时执行,即会同时调用两个rdbSave,防止产生竞争条件。
其次,在BGSAVE期间也是不可以执行客户端发送的BGSAVE,因为也是会产生竞争条件。
最后的就是BGREWRITEAOF和BGSAVE两个命令是不可以同时执行的,情况分以下两种
- 如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行
- 如果BGREWRITEAOF命令正在执行,那么客户端发送的BUSAVE命令会被服务器拒绝
虽然BGREWRITEAOF和BGSAVE两个命令的实际工作都是由子进程执行的,这两个命令在操作方面并没有什么冲突的地方,但是不能同时执行他们只是一个性能方面的考虑,并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作,这会很消耗CPU资源,不是一个好主意(至于为什么更倾向于执行BGREWRITEAOF,可能Redis设计者觉得AOF的优先级比较高吧)。
RDB文件载入时的服务器状态
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。
自动间隔性保存
SAVE命令与BGSAVE命令主要的区别其实就是SAVE命令是服务器进程去执行工作的,会阻塞服务器进程影响服务,而BGSAVE是服务器进程开启了另一个进程去执行的,不会影响服务器进程。
Redis允许用户通过设置服务器配置的SAVE选项,让服务器每隔一段时间自动执行一次BGSAVE命令,而且SAVE选项可以有多个保存条件(触发执行BGSAVE命令条件),只要其中一个条件被满足,服务器就会执行BGSAVE命令
redis默认的配置如下

那么只要满足以下三个条件之一,BGSAVE命令就会被执行
- SAVE 900 1:服务器在900秒之内(15分钟),只要服务器进行了至少一次修改(新增修改和删除键)
- SAVE 300 10:服务器在300秒之内(5分钟),只要服务器进行了至少十次修改(新增修改和删除键)
- SAVE 60 10000:服务器在60秒之内(1分钟),只要服务器进行了至少一万次修改(新增修改和删除键)
每次触发都会重写RDB文件(原来的数据清空,完整将数据重新放入)
也就是60秒检查一次,300秒检查一次,900秒检查一次,符合条件就会重写RDB文件,重写了之后重新计算时间
设置保存条件
Redis服务器启动后,用户可以通过指定配置文件或者传入启动参数的方式去设置save选项
接着,服务器会根据save选项所设置的保存条件,设置服务器状态redisServer对象(前面提过其存储服务器状态,里面有数据库数组)里面的saveparams属性
struct redisServer{
//。。。
。。。
//记录了保存条件的数组
struct saveparam *saveparams;
};
saveparams参数是一个saveparam数组,一个saveparam保存了一个save选项设置的保存条件
struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
};
比如save 900 1,那么saveparam结构里面的属性就是seconds = 900,changes = 1。
像Redis的默认配置的话,saveparams结构就如下所示

dirty计数器和lastsave属性
除了saveparams数组外,redisServer里面还维持着一个dirty计数器以及一个lastsave属性
- dirty,翻译过来就是脏嘛,脏其实就代表内存中的数据与磁盘中的数据不一致,dirty属性其实记录的是距离最近一次执行成功的BGSAVE和SAVE命令之后,服务器对数据库状态(所有的数据库)进行了多少次修改操作(新增、删除和修改键值对)
- lastsave,翻译过来就是最新的一次保存,lastsave属性是一个UNIX时间戳,记录的是服务器上一次成功执行SAVE或者BGSAVE命令的时间戳
struct redisServer{
//...
...
//保存选项的数组
struct saveparam *saveprams;
//dirty计数器
long long dirty;
//lastsave属性
time_t lastsave;
//...
...
};
当服务器(所有数据库)执行了一个数据库修改命令后,dirty属性就会进行更新,命令修改了多少次数据库,dirty计数器的值就增加多少
举个栗子
//dirty属性加1
set msg hello
//dirty属性加3,新增了3个
sadd msg 1 2 3
当执行完了SAVE或者BGSAVE命令之后,dirty属性就会归0,lastsave属性就会记录此时完成命令的时间戳
检查条件是否满足
Redis服务器拥有一个周期性的函数serverCron,默认的是每隔100毫秒就会执行一次,这个函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否满足,如果满足的话,就执行BGSAVE命令
def serverCron();
//前面的执行操作
....
//遍历所有save选项去判断是否满足
for saveparams in server.saveparams;
//先计算上次执行SAVE或者BGSAVE命令的时间间隔
//使用当前时间减去上一次在执行SAVE或者BGSAVE的时间(注意使用的是时间戳)
//这里是要将这个时间放在循环里面的,因为下一次save选项可能会基于此次执行SAVE或者BGSAVE
save_interval = unixtime_now() - server.lastsave;
//接下来就是一系列的判断
//判断时间和更改操作的数量是否达到save命令的选项
if(save_interval > saveparam.seconds and server.dirty >= saveparam.changes)
//执行BGSAVE
BGSAVE();
//更新最新一次BGSAVE或SAVE执行的时间,即lastsave属性
//更新dirty属性,为0
end if
//...
...
end serverCron;
本文详细介绍了Redis的RDB持久化机制,包括RDB文件的创建与加载过程,SAVE与BGSAVE命令的区别,自动保存设置及条件检查流程。通过了解这些内容,读者可以更好地掌握Redis数据持久化的实现原理。
855

被折叠的 条评论
为什么被折叠?



