您的位置:首页 > 数据库 > Redis

redis学习笔记(17)---RDB持久化

2016-05-12 17:16 246 查看

1、RDB文件

  redis是一个基于内存的数据库,数据库中的所有数据都是保存在内存中的。

  当进程退出时,内存中的数据库状态也会全部丢失。为了解决这个问题,redis提供了RDB持久化功能,RDB持久化可以将redis保存在内存中的数据存储到磁盘上,避免数据意外丢失。

  通过该文件,可以还原RDB文件生成时的数据库状态。

  


2、RDB文件的创建

  有两个命令可以生成RDB文件:SAVE和BGSAVE。

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

  而BGSAVE则会fork出一个子进程,然后子进程负责RDB文件的创建,父进程继续处理请求。

2.1、SAVE  

  SAVE命令最终由rdbSave()实现

  1)首先创建rdb文件

  2)调用rdbSaveRio()将全部数据库中全部的数据都写到rdb文件中

  3)调用flush、sync、close将缓冲区全部刷新到文件中

  4)将rdb文件命名为filename

int rdbSave(char *filename) {
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");// 1、创建文件

rioInitWithFile(&rdb,fp);
if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {//2、写文件
goto werr;
}

if (fflush(fp) == EOF) goto werr;  //3、刷新buffer
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;

if (rename(tmpfile,filename) == -1) {  //4、重命名
return REDIS_ERR;
}
return REDIS_OK;
werr:
return REDIS_ERR;
}


2.2、BGSAVE

  BGSAVE命令的实现如下:

  1)调用fork,创建子进程

  2)子进程调用rdbSave,相当于执行SAVE命令

  3)父进程返回继续处理请求

int rdbSaveBackground(char *filename) {
if ((childpid = fork()) == 0) {  //fork子进程
/* Child */
closeListeningSockets(0);  //关闭监听socket
redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename);  //完成save操作
exitFromChild((retval == REDIS_OK) ? 0 : 1);
} else {
/* Parent */
if (childpid == -1) {
return REDIS_ERR;
}
updateDictResizePolicy();
return REDIS_OK;
}
return REDIS_OK; /* unreached */
}


  当BGSAVE命令正在执行时,client端发送的SAVE、BGSAVE命令都会被server拒绝,避免产生竞争。

  同时,当BGSAVE命令正在执行时,BGREWRITEAOF命令会被延迟到BGSAVE执行完后执行。但是如果正在执行BGREWRITEAOF时,BGSAVE命令会被拒绝

2.3、服务器自动周期性保存

  除了通过client发送SAVE、BGSAVE命令,来让server执行保存操作外,还可以通过配置服务器的save选项,让server每隔一定时间自动执行一次BGSAVE命令。

  


  


3、设置保存条件

  在redisServer结构中,通过变量saveparams可以保存所有的save选项  

struct redisServer {
......
struct saveparam *saveparams;   /* Save points array for RDB */
int saveparamslen;
long long dirty;   /* Changes to DB from the last save */
time_t lastsave;   /* Unix time of last successful save */
......
};
struct saveparam {
time_t seconds;  //秒数
int changes;     //修改数
};


  然后在serverCron()函数中

  1)遍历参数表

  2)将每个参数与server中保存的dirty数和lastsave时间戳进行比较

  3)根据条件判断是否需要执行 rdbSaveBackground()完成保存操作

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* ......   */
/* 判断是否正在执行BGSAVE或BGREWRITEAOF命令 */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {

} else {
/* 没有在执行BGSAVE等命令时 */
for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;

//遍历参数表,若满足条件,则执行rdbSaveBackground
if (server.dirty >= sp->changes && server.unixtime-server.lastsave > sp->seconds &&
(server.unixtime-server.lastbgsave_try >REDIS_BGSAVE_RETRY_DELAY ||server.lastbgsave_status == REDIS_OK))
{
rdbSaveBackground(server.rdb_filename);
break;
}
}
/*  ......  */
}


4、RDB文件结构

  RDB文件的结构如下图所示:

  


  
REDIS:为”REDIS” 5个字符,用于判断文件是否为RDB文件
db_version:4字节,记录RDB文件的版本号,如0006表示第6版
databases:包含0个或多个数据库,及其中所有的key-value对数据
EOF:1字节,标志RDB文件正文内容的结束
check_sum:8字节,由前面4部分的所有数据计算得到

4.1、databases部分:

  databases部分包含0个或多个数据库的内容,其中每个database的结构如下

  


SELECTDB:1字节,标志后面为数据库ID
db_number:数据库ID,可以为1、 2 、5字节
key-value pairs:数据库中所有的key-value对数据

  

本文所引用的源码全部来自Redis3.0.7版本

redis学习参考资料:

https://github.com/huangz1990/redis-3.0-annotated

Redis 设计与实现(第二版)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: