做为web开发的一员,相信你们的面试经历里少不了会遇到这个问题:redis是怎么作持久化的?php
不急着给出答案,先停下来思考一下,而后再看看下面的介绍。但愿看了这边文章后,你可以回答这个问题。html
因为Redis是一种内存型数据库,即服务器在运行时,系统为其分配了一部份内存存储数据,一旦服务器挂了,或者忽然宕机了,那么数据库里面的数据将会丢失,为了使服务器即便忽然关机也能保存数据,必须经过持久化的方式将数据从内存保存到磁盘中。web
对于进行持久化的程序来讲,数据从程序写到计算机的磁盘的流程以下:面试
一、客户端发送一个写指令给数据库(此时数据在客户端的内存)redis
二、数据库接收到写的指令以及数据(数据此时在服务端的内存)数据库
三、数据库发起一个系统调用,把数据写到磁盘(此时数据在内核的内存)缓存
四、操做系统把数据传输到磁盘控制器(数据此时在磁盘缓存中)安全
五、磁盘控制器执行真正写入数据到物理媒介的操做(如磁盘)服务器
若是只是考虑数据库层面,数据在第三阶段以后就安全了,在这个时候,系统调用已经发起了,即便数据库进程奔溃了,系统调用会继续进行,也能顺利将数据写入到磁盘中。
在这一步以后,在第4步内核会将数据从内核缓存保存到磁盘缓存中,但为了系统的效率问题,默认状况下不会太频繁地执行这个动做,大概会在30s执行一次,这就意味着若是这一步失败了或者就在进行这一步的时候服务器忽然关机了,那么就可能会有30s的数据丢失了,这种比较普通的灾难性问题也是须要考虑的。app
POSIX API也提供了一个系统调用让内核强制将缓存数据写入到磁盘中,比较常见的就是fsync系统调用。
int fsync(int fd);
fsync函数只对由文件描述符fd指定的一个文件起做用,而且等待写磁盘操做结束后才返回。每次调用fsync时,会初始化一个写操做,而后把缓冲区的数据写入到磁盘中。fsync()函数在完成写操做的时候会阻塞进程,若是其余线程也在写同一个文件,它也会阻塞其余线程,直到完成写操做。
持久化是将程序数据在持久状态和瞬时状态间转换的机制。对于程序来讲,程序运行中数据是在内存的,若是没有及时同步写入到磁盘,那么一旦断电或者程序忽然奔溃,数据就会丢失了,只有把数据及时同步到磁盘,数据才能永久保存,不会由于宕机影像数据的有效性。而持久化就是将数据从程序同步到磁盘的一个动做过程。
redis有RDB和AOF两种持久化方式。RDB是快照文件的方式,redis经过执行SAVE/BGSAVE命令,执行数据的备份,将redis当前的数据保存到*.rdb
文件中,文件保存了全部的数据集合。AOF是服务器经过读取配置,在指定的时间里,追加redis写操做的命令到*.aof
文件中,是一种增量的持久化方式。
RDB文件经过SAVE或BGSAVE命令实现。
SAVE命令会阻塞Redis服务进程,直到RDB文件建立完成为止。
BGSAVE命令经过fork子进程,有子进程来进行建立RDB文件,父进程和子进程共享数据段,父进程继续提供读写服务,子进程实现备份功能。BGSAVE阶段只有在须要修改共享数据段的时候才进行拷贝,也就是COW(Copy On Write)。SAVE建立RDB文件能够经过设置多个保存条件,只要其中一个条件知足,就能够在后台执行SAVE操做。
SAVE和BGSAVE命令的实现代码以下:
void saveCommand(client *c) { // BGSAVE执行时不能执行SAVE if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); return; } rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); // 调用rdbSave函数执行备份(阻塞当前客户端) if (rdbSave(server.rdb_filename,rsiptr) == C_OK) { addReply(c,shared.ok); } else { addReply(c,shared.err); } } /* * BGSAVE 命令实现 [可选参数"schedule"] */ void bgsaveCommand(client *c) { int schedule = 0; /* 当AOF正在执行时,SCHEDULE参数修改BGSAVE的效果 * BGSAVE会在以后执行,而不是报错 * 能够理解为:BGSAVE被提上日程 */ if (c->argc > 1) { // 参数只能是"schedule" if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) { schedule = 1; } else { addReply(c,shared.syntaxerr); return; } } // BGSAVE正在执行,不操做 if (server.rdb_child_pid != -1) { addReplyError(c,"Background save already in progress"); } else if (server.aof_child_pid != -1) { // aof正在执行,若是schedule==1,BGSAVE被提上日程 if (schedule) { server.rdb_bgsave_scheduled = 1; addReplyStatus(c,"Background saving scheduled"); } else { addReplyError(c, "An AOF log rewriting in progress: can't BGSAVE right now. " "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever " "possible."); } } else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {// 不然调用rdbSaveBackground执行备份操做 addReplyStatus(c,"Background saving started"); } else { addReply(c,shared.err); } }
有了RDB文件以后,若是服务器关机了,或者须要新增一个服务器,从新启动数据库服务器以后,就能够经过载入RDB文件恢复以前备份的数据。
可是bgsave会耗费较长时间,不够实时,会致使在停机的时候丢失大量数据。
RDB文件保存的是数据库的键值对数据,AOF保存的是数据库执行的写命令。
AOF的实现流程有三步:
append->write->fsync
append追加命令到AOF缓冲区,write将缓冲区的内容写入到程序缓冲区,fsync将程序缓冲区的内容写入到文件。
当AOF持久化功能处于开启状态时,服务器每执行完一个命令,就会将命令以协议格式追加写入到redisServer结构体的aof_buf缓冲区,具体的协议这里不展开阐述。
AOF的持久化发生时期有个配置选项:appendfsync。该选项有三个值:
always:全部内容写入并同步到aof文件
everysec:将aof_buf缓冲区的内容写入到AOF文件,若是距离上次同步AOF文件的
no:将aof_buf缓冲区中的全部内容写入到AOF文件,但并不对AOF文件进行同步,由操做系统决定什么时候进行同步,通常是默认状况下的30s。
AOF持久化模式每一个写命令都会追加到AOF文件,随着服务器不断运行,AOF文件会愈来愈大,为了不AOF产生的文件太大,服务器会对AOF文件进行重写,将操做相同key的相同命令合并,从而减小文件的大小。
举个例子,要保存一个员工的名字、性别等信息:
> hset employee_12345 name "hoohack" > hset employee_12345 good_at "php" > hset employee_12345 gender "male"
只是录入这个哈希键的状态,AOF文件就须要保存三条命令,若是还有其余,好比删除,或者更新值的操做,那命令将会更多,文件会更大,有了重写后,就能够适当地减小文件的大小。
AOF重写的实现原理是先服务器中的数据库,而后遍历数据库,找出每一个数据库中的全部键对象,获取键值对的键和值,根据键的类型对键值对进行重写。好比上面的例子,能够合并为下面的一条命令:
> hset employee_12345 name "hoohack" good_at "php" gender "male"。
AOF的重写会执行大量的写入操做,Redis是单线程的,因此若是有服务器直接调用重写,服务器就不能处理其余命令了,所以Redis服务器新起了单独一个进程来执行AOF重写。
Redis执行重写的流程:
在子进程执行AOF重写时,服务端接收到客户端的命令以后,先执行客户端发来的命令,而后将执行后的写命令追加到AOF缓冲区中,同时将执行后的写命令追加到AOF重写缓冲区中。
等到子进程完成了重写工做后,会发一个完成的信号给服务器,服务器就将AOF重写缓冲区中的全部内容追加到AOF文件中,而后原子性地覆盖现有的AOF文件。
RDB持久化方式能够只经过服务器读取数据就能加载备份中的文件到程序中,而AOF方式必须建立一个伪客户端才能执行。
RDB的文件较小,保存了某个时间点以前的数据,适合作灾备和主从同步。
RDB备份耗时较长,若是数据量大,在遇到宕机的状况下,可能会丢失部分数据。另外,RDB是经过配置使达到某种条件的时候才执行,若是在这段时间内宕机,那么这部分数据也会丢失。
AOF方式,在相同数据集的状况下,文件大小会比RDB方式的大。
AOF的持久化方式也是经过配置的不一样,默认配置的是每秒同步,最快的模式是同步每个命令,最坏的方式是等待系统执行fsync将缓冲同步到磁盘文件中,大部分操做系统是30s。一般状况下会配置为每秒同步一次,因此最多会有1s的数据丢失。
RDB和AOF方式结合。起一个定时任务,每小时备份一份服务器当前状态的数据,以日期和小时命名,另外起一个定时任务,定时删除无效的备份文件(好比48小时以前)。AOF配置为1s一次。这样一来,最多会丢失1s的数据,同时若是redis发生雪崩,也能迅速恢复为前一天的状态,不至于中止服务。
Redis的持久化方案也不是一成不变的,纸上的理论还须要结合实践成果来证实其可行性。
原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。
更多精彩内容,请关注我的公众号。
参考文章:
http://oldblog.antirez.com/post/redis-persistence-demystified.html
http://blog.httrack.com/blog/2013/11/15/everything-you-always-wanted-to-know-about-fsync/