Redis是内存数据库,它将自己的数据存储在内存中,如果不想办法把数据保存到磁盘里面,一旦服务器进程退出,那么服务器的数据就会消失不见。为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据保存到磁盘中,防止数据丢失。
Redis持久化既可以手动执行,也可以根据服务器配置定期执行,该功能可以将某个时间点的数据保存到一个RDB文件中。
Redis持久化功能所生成的RDB文件是一个经过压缩的二进制文件,可以通过该文件还原生成RDB文件时的数据。因为RDB文件存放在磁盘中,所以即使宕机,我们也可以通过RDB文件恢复Redis数据。
1、RDB文件的创建和载入
1)创建
有两个Redis命令可以生成RDB文件,一个是SAVE,一个是BGSAVE
- SAVE命令会阻塞服务进程,期间服务器不能处理任何命令请求。只有执行完SAVE命令之后,客户端发送的命令才会被执行。
func SAVE(){ // 创建rdb文件 rdbSAVE() }
- BGSAVE命令不会阻塞服务进程,会派生出一个子进程,然后由子进程创建RDB文件,服务器进程继续处理命令请求
def BGSAVE () : # 创建子进程 pid = forkl if pid == 0: # 子进程货责创建RDB 文件 rdbsave() # 完成之后向父进程发送信号 signal_parent () elif pid > 0: # 父进程經续处理命令请求,并通过轮询等待子进程的信号 kandle_request_and_wait_signal() else: #处理出错情况 handle_fork_error()
2)载入
RDB文件的载入是在服务器启动时自动执行的,只要Redis服务器在启动时检测到RDB文件的存在,它就会自动载入RDB文件。服务器在载入RDB另外AOF文件的更新频率比RDB更新频率高,所以:
- 如果服务器开启AOF持久化功能,那么优先使用AOF文件还原数据
- 只有在AOF功能关闭时,服务器才会使用RDB文件来还原数据
载入RDB文件的实际工作由rdb.c/rdbLoad函数完成。
2、BGSAVE自动间隔保存的底层实现
2.1 save选项配置
BGSAVE命令可以在不阻塞服务进程的情况下执行,所以Redis允许用户通过设置服务器的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。例如,如果我们向服务器提供以下配置:
时间间隔 次数 save 900 1 save 300 10 save 60 10000
那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:
- 服务器在900秒内,对数据库进行了至少1次的修改。
- 服务器在300秒内,对数据库进行了至少10次的修改。
- 服务器在600秒内,对数据库进行了至少10000次的修改。
2.2 保存save配置的底层结构
服务器程序会根据save选项,设置服务器状态redisService结构的saveparams属性:
structredisService{ //记录了保存条件的数组structsaveparam*saveparams; };
saveparams属性是一个数据,数组中的每个元素都是一个saveparam结构,每个saveparam结构都保存了一个save选项设置的保存条件:
structsaveparam{ // 秒数time_tseconds; // 修改数intchanges; };
上面举例的save选项的saveparams结构如下:
2.3 BGSAVE自动间隔性保存的实现流程
BGSAVE实现需要两个步骤:
- dirty计数器和lastsave记录已修改的次数和上次执行save/bgsave的时间戳
- 检查是否满足保存条件
2.3.1 dirty计数器和lastsave属性
服务器除了saveparams数组之外,还维持着一个dirty计数器和一个lastsave属性:
- dirty计数器记录距离上一次成功执行SAVE/BGSAVE命令之后,服务器读数据库进行了多少次修改(包括写入、删除、更新等操作)。
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE/BGSAVE命令的时间
structredisServer{ //修改计数器longlongdirty; // 上一次执行保存时间time_tlastlave; };
当服务器成功执行了一个数据库修改命令之后,程序就会对dirty计数器进行更新:命令修改了多少次数据库,dirty计数器的值就会增加多少。
上图展示了服务器包含的dirty计数器和lastsave属性,说明如下:
- dirty计数器的值为123,表示服务器在上次保存之后,对数据库状态进行了123次修改。
- lastsave属性则记录了服务器上次执行保存操作的时间为1378270800.
2.3.2 检查是否满足保存条件
Redis的服务器周期操作函数serverCron默认每隔100毫秒就会执行一次,它的其中一项工作就是检查save选项中设置的保持条件是否满足,如果满足就执行BGSAVE命令。
serverCron函数检查保存条件的过程伪代码:
defserverCron(): # 遍历所有保存条件forsaveparaminserver.saveparams: # 计算距离上次执行保存操作有多少秒save_interval=time.time() -saver.lastsave# 如果数据图的修改次数超过条件所设置的次数# 并且距离上次保存的时间超过条件所设置的时间#那么执行保存操作if ( server.dirty>=serparam.changesandsave_interval>saveparam.seconds ): BGSAVE()
程序会遍历检查saveparams数组中的所有保存条件,只要有任意一个条件被满足,那么服务器就会执行BGSAVE命令
3 RDB文件结构
RDB文件名为:dump.rdb;下图展示了一个完整的RDB文件包含的各个部分:
- REDIS:5字节,程序在载入文件时,可以通过这5个字节判断是否是RDB文件。
- db_version:4字节,记录RDB文件的版本号。
- databases:redis的数据库,包含各个数据库中的键值对数据。
- EOF:1字节,标志RDB正文的结束。
- check_sum:用于检查RDB文件是否损坏或者出错。
3.1 databases结构
下图展示了databases结构:
- SELECTDB:1字节,当程序遇到这个值,它将会知道接下来要读的是一个数据库号码。
- db_number:数据库号码,服务器会调用SELECT,根据读入的数据库号码进行数据库切换。
- key_value_pairs:保存了数据库中的所有键值对数据。
下图展示了一个完整的RDB文件结构:
3.2 key_value_pairs结构
RDB文件中的每个key_value_pairs部分都保存了一个或以上数量的键值对,如果键值对带有过期时间的话,那么键值对的过期时间也会被保存在内。
- 不带过期时间的键值对:
- 带过期时间的键值对
其中TYPE表示该键值对的类型