众所周知,Redis是内存数据库,且使用单个线程来处理命令请求。它将本身的数据库状态(非空数据库以及它们的键值对)存储在内存里面。因此若是没有持久化机制,不把数据保存到硬盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。
为了解决这个问题,redis 提供两种方法进行数据持久化,分别是RDB和AOF。RDB能够将Redis在内存中的数据库状态保存到磁盘里面以实现持久化,AOF经过记录写命令达到持久化效果。两种方法都有各自的优势,须要咱们在生产环境中依照实际的业务状况进行裁定。服务器按照下面的流程图选择持久化方式:web
RDB持久化既能够手动执行,也能够依据配置文件选项按期执行。RDB持久化生成的RDB文件是一个二进制文件,经过此二进制文件可以还原数据库状态。
如图:
RDB提供两种方式生成RDB文件,分别经过执行SAVE和BGSAVE命令生产RDB文件。SAVE由服务器进程直接执行保存,它会阻塞服务器的进程。BGSAVE是服务器子进程执行保存操做,它的运行不会阻塞服务器进程的运行。redis
上面已经说过,SAVE命令执行时会阻塞服务器的运行。所以,在服务器执行SAVE命令时,客户端发送的全部命令请求都会被拒绝。只有服务器执行完SAVE命令,服务器才会从新接收客户端发送的命令请求。数据库
与SAVE相反,BGSAVE由服务器子进程执行,在执行时依然能够接收客户端发送命令并处理。可是,服务器处理SAVE、BGSAVE、BGREWRITEAOF三种方式和平时有些不同。
数组
对于SAVE,在执行BGSAVE时,客户端发送的SAVE命令会被服务器拒绝。服务器禁止SAVE和BGSAVE命令同时执行是为了防止父进程(服务器进程)和子进行同时执行产生竞争条件
安全
对于BGSAVE。在执行BGSAVE时,客户端发送的BGSAVE命令时一样也是被拒绝。缘由是为了防止两个BGSAVE产生竞争条件。
服务器
对于BGREWRITEAOF。在执行BGSAVE时,也是被拒绝的。缘由是由于BGSAVE和BGREWRITEAOF不能同时执行。
app
在陈述原理以前,先来看下几个与按期持久化密切相关的属性。
函数
saveparams属性
在服务启动后,服务器会读取save的值赋给saveparams。save 是redis.conf
中的一个配置文件,它在服务器中的默认配置为:性能
save 900 1 //服务器在900秒以内,对数据库进行了至少1次修改。 save 300 10 //服务器在300秒以内,对数据库进行了至少10次修改。 save 60 10000 //服务器在60秒以内,对数据库进行了至少10000次修改。
服务器状态redisServer中saveparams结构以下:
spa
struct redisServer { // ... // 记录了保存条件的数组 struct saveparam *saveparams; // ... }; struct saveparam { // 秒数 time_t seconds; // 修改数 int changes; };
若是服务器中SAVE属性为默认配置,那么服务器中的状态将会下面这样的。
dirty计数器和lastsave属性
除了saveparams数组以外,服务器状态还维持着一个dirty计数器,以及一个lastsave属性:
dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令以后,服务器对数据库状态(服务器中的全部数据库)进行了多少次修改(包括写入、删除、更新等操做)。
lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。
dirty和lastsave在服务器状态中的结构以下:
struct redisServer { // ... // 修改计数器 long long dirty; // 上一次执行保存的时间 time_t lastsave; // ... };
上面已经介绍完几个重要的属性了,如今开始切入正题了。
若是未开启AOF功能,那么在Redis启动后,Redis的服务器周期性操做函数serverCron默认每隔100毫秒就会执行一次,它的其中一项工做就是检查save选项所设置的保存条件是否已经知足。若是知足的话,就执行BGSAVE命令。下面是ServerCron函数的逻辑代码
def serverCron(): # … # 遍历全部保存条件 for saveparam in server.saveparams: # 计算距离上次执行保存操做有多少秒 save_interval = unixtime_now()-server.lastsave # 若是数据库状态的修改次数超过条件所设置的次数 # 而且距离上次保存的时间超过条件所设置的时间 # 那么执行保存操做 if server.dirty >= saveparam.changes and \ save_interval > saveparam.seconds: BGSAVE(); # ...
举个例子,若是Redis服务器的当前状态以下图所示
那么当时间来到1378271101,也便是1378270800的301秒以后,服务器将自动执行一次BGSAVE命令,由于saveparams数组的第二个保存条件——300秒以内有至少10次修改——已经被知足。
假设BGSAVE在执行5秒以后完成,那么上图所示的服务器状态将更新为下图所示的状态,其中dirty计数器已经被重置为0,而lastsave属性也被更新为1378271106。
以上就是Redis服务器根据save选项所设置的保存条件,自动执行BGSAVE命令,进行间隔性数据保存的实现原理。
AOF持久化是经过保存Redis服务器所执行的写命令来记录数据库状态的。AOF持久化命令能够分为命令追加(append)、文件写入、文件同步三个步骤。开启AOF持久化的配置 appendonly yes
resist服务器中的结构是这样的
struct redisServer { // ... // AOF缓冲区 sds aof_buf; // ... }
服务器在执行一个写命令后,会将命令追加到 aof_buf 缓冲区的末尾。
将命令写入到aof文件中去
appendfsync no (同步操做交给数据库)
当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,因此这一切就彻底依赖于操做系统的调试了。对大多数Linux操做系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
appendfsync everysec (每隔一秒执行一次同步操做)
当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。可是当这一次的fsync调用时长超过1秒时。Redis会采起延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就无论会执行多 长时间都会进行。这时候因为在fsync时文件描述符会被阻塞,因此当前的写操做就会阻塞。
结论就是,在绝大多数状况下,Redis会每隔一秒进行一 次fsync。在最坏的状况下,两秒钟会进行一次fsync操做。这一操做在大多数数据库系统中被称为group commit,就是组合屡次写操做的数据,一次性将日志写到磁盘。
appendfsync always (每一次写操做都会执行同步操做) 置appendfsync为always时,每一次写操做都会调用一次fsync,这时数据是最安全的,固然,因为每次都会执行fsync,因此其性能也会受到响。