Redis(十三):RDB持久化(一)

本文详细介绍了Redis的RDB持久化机制,包括RDB文件的创建与加载过程,SAVE与BGSAVE命令的区别,自动保存设置及条件检查流程。通过了解这些内容,读者可以更好地掌握Redis数据持久化的实现原理。


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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值