作为web开发的一员,相信大家的面试经历里少不了会遇到这个问题:c5b83e7ad2de6c2c91.html">redis是怎么做持久化的?
不急着给出答案,先停下来思考一下,然后再看看下面的介绍。希望看了这边文章后,你能够回答这个问题。
为什么需要持久化?由于Redis是一种内存型数据库,即服务器在运行时,系统为其分配了一部分内存存储数据,一旦服务器挂了,或者突然宕机了,那么数据库里面的数据将会丢失,为了使服务器即使突然关机也能保存数据,必须通过持久化的方式将数据从内存保存到磁盘中。
对于进行持久化的程序来说,数据从程序写到计算机的磁盘的流程如下:
1、客户端发送一个写指令给数据库(此时数据在客户端的内存)
2、数据库接收到写的指令以及数据(数据此时在服务端的内存)
3、数据库发起一个系统调用,把数据写到磁盘(此时数据在内核的内存)
4、操作系统把数据传输到磁盘控制器(数据此时在磁盘缓存中)
5、磁盘控制器执行真正写入数据到物理媒介的操作(如磁盘)
如果只是考虑数据库层面,数据在第三阶段之后就安全了,在这个时候,系统调用已经发起了,即使数据库进程奔溃了,系统调用会继续进行,也能顺利将数据写入到磁盘中。
在这一步之后,在第4步内核会将数据从内核缓存保存到磁盘缓存中,但为了系统的效率问题,默认情况下不会太频繁地执行这个动作,大概会在30s执行一次,这就意味着如果这一步失败了或者就在进行这一步的时候服务器突然关机了,那么就可能会有30s的数据丢失了,这种比较普通的灾难性问题也是需要考虑的。
POSIX API也提供了一个系统调用让内核强制将缓存数据写入到磁盘中,比较常见的就是fsync系统调用。
int fsync(int fd);
fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束后才返回。每次调用fsync时,会初始化一个写操作,然后把缓冲区的数据写入到磁盘中。fsync()函数在完成写操作的时候会阻塞进程,如果其他线程也在写同一个文件,它也会阻塞其他线程,直到完成写操作。
持久化持久化是将程序数据在持久状态和瞬时状态间转换的机制。对于程序来说,程序运行中数据是在内存的,如果没有及时同步写入到磁盘,那么一旦断电或者程序突然奔溃,数据就会丢失了,只有把数据及时同步到磁盘,数据才能永久保存,不会因为宕机影像数据的有效性。而持久化就是将数据从程序同步到磁盘的一个动作过程。
Redis的持久化redis有RDB和AOF两种持久化方式。RDB是快照文件的方式,redis通过执行SAVE/BGSAVE命令,执行数据的备份,将redis当前的数据保存到*.rdb文件中,文件保存了所有的数据集合。AOF是服务器通过读取配置,在指定的时间里,追加redis写操作的命令到*.aof文件中,是一种增量的持久化方式。
RDBRDB文件通过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); } }