RDB简介
RDB的全称是Redis Database File,redis的RDB刷盘为异步刷盘,配置N秒内产生了M次更新操作则触发刷盘。
redis内部会每100ms进行一次检测。判断是否需要刷盘。
刷盘的配置:
save 900 1 //表示900s内有一次修改
save 300 10 //表示300s内有10次修改
save 60 10000 //表示60s内有10000次修改
满足上述条件后就会通过BGSAVE命令刷盘
刷盘有两个命令,SAVE和BGSAVE(Background Save)
- redis的save命令会导致阻塞,无法执行其他的命令。
- redis的bgsave命令会开启新的进程来进行备份,由于新的进程的数据空间会和父进程的独立,故相当于刷盘的数据是这一刻的快照。
- bgsave和bgrewriteaof命令都是生成子进程处理刷盘,两个命令不会同时运行。
- 如果bgrewriteaof正在运行的时候执行bgsave命令,该命令会被拒绝。如果bgsave正在运行的时候执行bgrewriteaof命令,该命令会被阻塞到bgsave命令运行完毕后执行。
- redis的rdb文件在执行redis-serverD目录位置下,可以用这个命令来寻找
find / -name dump.rdb
RDB文件说明
简要分析rdb文件:
REDIS 表示这个文件是redis的rdb文件。
0006 表示版本号
379 表示下一个字节是数据库编号
0 表示是第0个db
0 表示保存的类型的REDIS_DB_TYPE_STRING
003 表示下一个字符串长度为3个字符
msg 表示是key的字符
005 表示下一个字符串长度为5个字符
hello 表示value的字符
337 表示的是EOF常量
剩余的8个字符表示的是文件校验,用于判断文件是否完整
RDB过程代码分析
int rdbSaveBackground(char *filename) {
pid_t childpid;
long long start;
// 如果 BGSAVE 已经在执行,那么出错
if (server.rdb_child_pid != -1) return REDIS_ERR;
// 记录 BGSAVE 执行前的数据库被修改次数
server.dirty_before_bgsave = server.dirty;
// 最近一次尝试执行 BGSAVE 的时间
server.lastbgsave_try = time(NULL);
// fork() 开始前的时间,记录 fork() 返回耗时用
start = ustime();
if ((childpid = fork()) == 0) {
int retval;
/* Child */
// 关闭监听的fd
closeListeningSockets(0);
// 设置进程的标题,方便识别
redisSetProcTitle("redis-rdb-bgsave");
// 执行保存操作
retval = rdbSave(filename);
// 向父进程发送信号
exitFromChild((retval == REDIS_OK) ? 0 : 1);
} else {
/* Parent */
// 计算 fork() 执行的时间
server.stat_fork_time = ustime()-start;
// 打印 BGSAVE 开始的日志
redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);
// 记录数据库开始 BGSAVE 的时间
server.rdb_save_time_start = time(NULL);
// 记录负责执行 BGSAVE 的子进程 ID
server.rdb_child_pid = childpid;
// 关闭自动 rehash
updateDictResizePolicy();
return REDIS_OK;
}
return REDIS_OK; /* unreached */
}
说明:
避免出现子进程对父进程的socket产生并发的竞争情况,出现客户端同时连接到子进程和父进程的情况,fork后的第一步,子进程就要close父进程的监听fd,包括,监听的socket,监听本地连接的socket,集群的socket。
设置静态变量中的进程标题。
rdbSave函数则是主要的功能函数
int rdbSave(char *filename) {
// 创建临时文件 fp = fopen(tmpfile,"w"); // 初始化 I/O,变量rdb为rdb文件的处理状态 rioInitWithFile(&rdb,fp); // 写入 RDB 版本号 snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION); rdbWriteRaw(&rdb,magic,9) == -1; // 遍历所有数据库 for (j = 0; j < server.dbnum; j++) { //遍历每个数据库,将key,value,超时时间写入文件中 } di = NULL; /* So that we don't release it again on error. */ /* * 写入 EOF 代码 */ rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF); // 冲洗缓存,确保数据已写入磁盘 if (fflush(fp) == EOF) goto werr; if (fsync(fileno(fp)) == -1) goto werr; if (fclose(fp) == EOF) goto werr; /* * 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。 */ (rename(tmpfile,filename); // 写入完成,打印日志 redisLog(REDIS_NOTICE,"DB saved on disk"); // 清零数据库脏状态 server.dirty = 0; // 记录最后一次完成 SAVE 的时间 server.lastsave = time(NULL); // 记录最后一次执行 SAVE 的状态 server.lastbgsave_status = REDIS_OK; return REDIS_OK;
}