redis

<Redis>RDB持久化

2019-02-13  本文已影响72人  但时间也偷换概念

前言:

Redis是一个键值对数据库服务器,因为它是内存数据库,它将数据都保存在内存里面,所以如果不想办法进行持久化,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。

为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

Redis持久化既可以手动执行,也可以根据配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中,RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。

从内存生成RDB文件到磁盘的主要函数是rdbSave

从磁盘还原数据库状态到内存的主要函数是rdbLoad

工作原理

·Redis调用fork(fork操作会阻塞),产生一个子进程

·子进程把数据写到一个临时的RDB文件

·当子进程写完新的RDB文件后,把旧的RDB文件替换掉。

SAVE&BGSAVE

有两个Redis命令可以用于生成RDB文件,一个是SAVE,一个是BGSAVE。

SAVE命令会阻塞Redis服务器进程,SAVE的过程不能处理任何命令请求,直到RDB文件创建完毕为止。

BGSAVE命令会fork出一个子进程,子进程负责创建RDB文件,父进程仍然可以处理命令请求。

创建RDB文件的实际工作由rdb.c/rdbSave函数完成,SAVE和BGSAVE命令会以不用的方式调用这个函数,下面用一段伪代码展示它们调用的区别:

def SAVE():

#创建RDB文件

rdbSave()

def BGSAVE():

#创建子进程

pid = fork()

if(pid==0):

       #子进程负责创建RDB文件

       rdbSave()

       #完成以后向父进程发送信号

       signal_parent()

elif pid > 0:

       #父进程继续处理命令请求,并通过轮询等待子进程的信号

       handle_request_and_wait_signal()

else:

       #处理出错情况

       handle_fork_error()

和使用SAVE或者BGSAVE命令创建RDB文件不同,RDB文件的载入工作是在服务器启动的时候自动执行的,所以Redis没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件的存在,它就会自动载入RDB文件。

另外,因为AOF文件的更新频率比RDB文件的更新频率高,所以:

·如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。

·只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。

载入RDB文件的实际工作由rdb.c/rdbLoad函数完成,这个函数和rdbSave函数之间的关系就是逆向的。

SAVE命令执行时的服务器状态

前面提到过,当SAVE命令执行时,Redis服务器会被阻塞,所以当SAVE命令正在执行时,客户端发送的所有命令请求都会被阻塞,只有在服务器执行完SAVE命令、重新开始接受命令请求之后,客户端发送的命令才会被处理。

BGSAVE命令执行时的服务器状态

因为BGSAVE的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求,但是,在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式会和平时有所不同。

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

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

最后,BGREWRITEAOF和BGSAVE两个命令不能同时执行:

·如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。

·如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。

BGREWRITEAOF和BGSAVE都是由子进程完成的,虽然没有什么冲突的地方,主要是出于性能的考虑,并发两个子进程同时执行大量的磁盘写入操作,不是什么好主意。

RDB文件载入期间的服务器状态

RDB文件载入期间Redis服务器处于阻塞状态,直到载入工作完成。


自动间隔性保存

用户可以通过save选项设置多个保存条件,只要其中任何一个条件被满足,服务器就会执行BGSAVE命令。

举个例子:

如果我们向服务器提供以下配置:

save 900 1

save 300 10

save 60 10000

那么只要服务器满足以下三个条件中的任意一个,BGSAVE命令就会被执行。

·服务器在900秒以内,对数据库进行了至少1次修改。

·服务器在300秒以内,对数据库进行了至少10次修改。

·服务器在60秒以内,对数据库进行了至少10000次修改。

这也是redis服务器的默认保存条件,如果用户进行了自定义的配置,将会采用用户的配置。

自动保存的实现

服务器程序会根据save选项的配置,设置服务器状态redisServer结构的saveParams属性。

struct saveParams{

 // 秒数

 time_t  seconds;

// 修改数 

int changes;

}

dirty计数器和lastsave属性

除了saveparams数组之外,服务器状态还维持着一个dirty计数器,以及一个lastsave属性。

·dirty计数器记录距离上一次成功执行SAVE或者BGSAVE命令之后,服务器对数据库状态进行了多少次修改

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

struct redisServer{

  // ....

   // 修改计数器

  long long dirty;

  // 上一次保存的时间

  time_t lastsave;

}

检查保存条件是否满足

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

以下伪代码展示了serverCron函数检查保存条件的过程:

def  serverCron():

     #...

    # 遍历所有保存条件

    for saveparam in server.saveparams:

              save_interval = unixtime_not() - server.lastsave

              // 如果修改次数超过条件且时间超过条件,就执行BGSAVE命令

              if  server.dirty >= saveparam.changes and \

                  save_interval > saveparam.seconds:

                   BGSAVE()

#....

重点回顾

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

·SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。

·BGSAVE命令由子进程执行保存操作,所以该命令不会阻塞服务器。

·服务器状态中会保存所有用save选项设置的保存条件,满足任意一个条件服务器就会自动执行BGSAVE命令。

·RDB文件是一个经过压缩的二进制文件,由多个部分组成。

·对于不同类型的键值对,RDB文件会使用不同的方式来保存他们。

·linux操作系统在fork子进程的时候使用“写时复制”技术,由于fork的过程父进程有写入操作,就会触发父子进程的复制操作,复制操作的效率跟父进程的内存大小挂钩,而且会阻塞。所以redis内存越大,fork阻塞的时间越长。

RDB的缺点:

如果你希望在Redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB 不适合你。

虽然你可以配置不同的Save 时间点(例如每隔5 分钟并且对数据集有100 个写的操作),但是Redis要完整的保存整个数据集是一个比较繁重的工作。你通常会每隔5 分钟或者更久做一次完整的保存,万一Redis意外宕机,你可能会丢失几分钟的数据。

·RDB需要经常 Fork子进程来保存数据集到硬盘上,当数据集比较大的时,Fork的过程是非常耗时的,可能会导致 Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU 性能不是很好的情况下,这种情况会持续1 秒,AOF也需要 Fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。

参考资料:

Scipathi Krishnan <Redis RDB 文件格式>

Scipathi Krishnan <Redis RDB 版本历史>

Redis作者博文<Redis persistence demystified>

黄健宏 <Redis设计与实现>

上一篇下一篇

猜你喜欢

热点阅读