Redis设计与实现-RDB持久化

198 阅读7分钟

RDB持久化

数据库状态:Redis键值对数据库服务器中包含任意多个非空数据库,每个非空数据库又包含任意多个键值对,我们将服务器中的非空数据库以及它们的键值对统称为数据库状态。

Redis是内存数据库,它将数据库状态存在内存里,一旦服务器退出,服务器的数据库状态就会消失,所以就需要将存储在内存中的数据库状态保存到磁盘中,而这就是我们需要的RDB持久化,用于把Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

RDB持久化可以通过手动执行,也能根据服务器配置选项定期执行,将某个时间点上的数据库状态保存到一个RDB文件上。我们可以通过这个文件还原数据库状态。

RDB文件的创建和载入

两个命令:SAVE、BGSAVE用于生存RDB文件。

  • SVAE

    会阻塞Redis服务器进程,知道RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令

  • BGSAVE

    派生出一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。

创建RDB文件由rdb.c/rdbSave函数实现,SAVEBGSAVE就是以不同的方式去调用这个函数,看看他们的伪代码吧:

def SAVE():
	# 创建RDB文件
	rdbSave();

def BGSAVE():
	# 创建子进程
	pid = fork();

	if(pid == 0):
		# 子进程负责创建RDB文件
		rdbSave();
		# 完成之后向父进程发送信号
		signal_parent();
	else pid > 0:
		# 主进程继续处理命令请求,并通过轮询等待子进程信号
	else:
		# 处理出错情况
		handle_fork_error();

关于RDB文件的载入:

RDB文件的载入时服务器启动的时候就自动执行的,Redis没有专门用于载入RDB文件的命令,当Redis在启动的时候检测到RDB文件存在,就会自动载入RDB文件。

注意: AOF 文件的更新频率通常比RDB文件高,所以

  • 如果服务器开启了AOF持久化功能的话,那么服务器会优先使用AOF文件来还原数据库状态
  • 只有在AOF持久化功能关闭的情况下,服务器才会使用RDB文件来还原数据库状态

载入RDB文件由rdb.c/rdbLoad函数完成,

image.png

服务器状态

  • SAVE

    会阻塞Redis服务器进程,知道RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令。只有在服务器执行完SAVE命令、重新开始接收命令请求之后,客户端发送的命令才会被处理。

  • BGSAVE

    • 派生出一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。在命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的处理方式和平时不同:

    • 在BGSAVE执行期间,SAVE命令会被服务器拒绝,防止父进程(服务器进程)和子进程同时执行两个rdbSave调用,发生竞争。

    • BGSAVE执行期间,BGSAVE也会被拒绝,原因和上述差不多

    • BGSAVE和BGREWRITEAOF不能同时执行

      • BGSAVE执行时,BGREWRITEAOF会被延迟到BGSAVE执行结束后执行
      • BGREWRITEAOF执行时,BGSAVE会被拒绝
    • 服务器在载入RDB文件时,会一直处于阻塞状态,知道载入工作完成为止

自动间隔性保存

Redis 可以根据save选项设置的保存条件,自动执行BGSAVE命令

设置保存条件

当Redis服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save选项,如果用户没有主动设置save选项,那么服务器会为save设置默认条件:

save 900 1
save 300 10
save 60 10000
# 这个表示当满足下列条件 BGSAVE就会被执行
    # 服务器在900秒之内,对数据库进行了至少一次修改
    # 服务器在300秒内,对数据库进行了至少10次修改
    # 服务器在60秒内,对数据库进行了至少10000次修改

接着,服务器程序会根据save选项所设置的保存条件,设置服务器状态redisServer结构的sasveparams属性:

struct redisServer{
    // ...
    // 记录了保存条件的数组
    struct saveparam *saveparams;
    // ...
};

sasveparams属性是一个数组,数组中的每一个元素都是一个sasveparam结构,,每个sasveparam结构都保存了一个save选项设置的保存条件:

struct saveparam{
    // 秒数
    time_t seconds;
    // 修改数
    int change;
};

dirty计数器和lastsave属性

  • dirty 计数器

    记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)

  • lastsave

    一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间

struct redisServer{
    // ...
    // 记录了保存条件的数组
    struct saveparam *saveparams;
    // 修改计数器
    long long dirty;
    // 上一次执行保存的时间
    time_t lashsave;
    // ...
};
1
2
3
4
5
6
7
8
9
10

当服务器成功执行了一个数据库修改命令之后,程就会对dirty计数器进行更新:命令修改了多少从数据库,dirty计数器的值就增加多少。

检查保存条件是否满足

Redis 的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,她的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足,就执行BGSAVE命令

def serverCron():
	# ...
	# 遍历所有保存条件
	for saveparam in server.saveparams:
		# 计算距离上次执行保存操作有多少秒
		save_initerval = unixtime_now() - server.lastsave;
		# 如果数据库状态的修改次数超过条件所设置的次数
		# 并且距离上次保存的时间超过条件所设置的时间
		# 那么执行保存操作
		if(server.dirty >= saveparam.changes and save_interval > saveparam.seconds):
			BGSAVE();
	# ...

RDB文件结构

分析RDB文件

介绍od命令:

  • 可以解析Redis服务器产生的RDB文件
  • 该命令用给定的格式转存(dump)并打印输入文件 人工分析RDB文件不是必须的,网上都有很多RDB处理文件 Redis也自带有RDB文件检查工具

总结

  1. RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据

  2. SAVE命令由服务器进程直接执行保存操作,该命令会阻塞服务器

  3. BGSAVE命令由子进程执行保存操作,不会阻塞服务器

    注意此时服务器的状态:在处理BGSAVE命令时,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令方式与平时不同。

    客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令与BGSAVE同时执行,是为了避免父进程与子进程同时执行rdbSave调用,产生竞争条件。

    客户端发送的BGSAVE命令也会被服务器拒绝,因为同时执行两个BGSAVE也会产生竞争条件。

    最后:BGSAVE和BGREWRITEAOF不能同时执行:因为两个命令实际工作都是由子进程执行,所以两个命令在操作方面没有冲突,但是并发出两个子进程,并且两个子进程都是同时执行大量的磁盘写入操作的话不是个好主意。

  4. 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足,服务器自动执行BGSAVE

  5. RDB文件时一个经过压缩的二进制文件,由多个部分组成

  6. 对于不同类型的键值对,RDB文件会使用不同方式保存