前置学习:Redis server启动源码-CSDN博客
1、触发时机
1、执行save命令--->rdbSave函数
2、执行bgsave命令--->rdbSaveBackground函数或者(serverCron->prepareForShutdown)
3,主从复制-->startBgsaveForReplication函数,落盘方式(rdbSaveBackground函数),不落盘方式(rdbSaveToSlavesSockets函数)。
4、flushall命令--->flushallCommand函数
5、正常关闭服务(serverCron->prepareForShutdown函数)
2、执行过程
在主进程(父进程)中fork一个子进程来生成rdb文件(二进制),父子进程通过管道来处理数据传输通信。
生成RDB文件过程
/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success ** 将数据库保存到磁盘上。** 保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。*/
int rdbSave(char *filename) {dictIterator *di = NULL;dictEntry *de;char tmpfile[256];char magic[10];int j;long long now = mstime();FILE *fp;rio rdb;uint64_t cksum;// 创建临时文件snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());fp = fopen(tmpfile,"w");if (!fp) {redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",strerror(errno));return REDIS_ERR;}// 初始化 I/OrioInitWithFile(&rdb,fp);// 设置校验和函数if (server.rdb_checksum)rdb.update_cksum = rioGenericUpdateChecksum;// 写入 RDB 版本号(魔数)snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;// 遍历所有数据库for (j = 0; j < server.dbnum; j++) {// 指向数据库redisDb *db = server.db+j;// 指向数据库键空间dict *d = db->dict;// 跳过空数据库if (dictSize(d) == 0) continue;// 创建键空间迭代器di = dictGetSafeIterator(d);if (!di) {fclose(fp);return REDIS_ERR;}/* Write the SELECT DB opcode ** 写入 DB 选择器*/if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;if (rdbSaveLen(&rdb,j) == -1) goto werr;/* Iterate this DB writing every entry ** 遍历数据库,并写入每个键值对的数据*/while((de = dictNext(di)) != NULL) {sds keystr = dictGetKey(de);robj key, *o = dictGetVal(de);long long expire;// 根据 keystr ,在栈中创建一个 key 对象initStaticStringObject(key,keystr);// 获取键的过期时间expire = getExpire(db,&key);// 保存键值对数据if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;}dictReleaseIterator(di);}di = NULL; /* So that we don't release it again on error. *//* EOF opcode ** 写入 EOF 代码*/if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;/* CRC64 checksum. It will be zero if checksum computation is disabled, the* loading code skips the check in this case. ** CRC64 校验和。** 如果校验和功能已关闭,那么 rdb.cksum 将为 0 ,* 在这种情况下, RDB 载入时会跳过校验和检查。*/cksum = rdb.cksum;memrev64ifbe(&cksum);rioWrite(&rdb,&cksum,8);/* Make sure data will not remain on the OS's output buffers */// 冲洗缓存,确保数据已写入磁盘if (fflush(fp) == EOF) goto werr;if (fsync(fileno(fp)) == -1) goto werr;if (fclose(fp) == EOF) goto werr;/* Use RENAME to make sure the DB file is changed atomically only* if the generate DB file is ok. ** 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。*/if (rename(tmpfile,filename) == -1) {redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));unlink(tmpfile);return REDIS_ERR;}// 写入完成,打印日志redisLog(REDIS_NOTICE,"DB saved on disk");// 清零数据库脏状态server.dirty = 0;// 记录最后一次完成 SAVE 的时间server.lastsave = time(NULL);// 记录最后一次执行 SAVE 的状态server.lastbgsave_status = REDIS_OK;return REDIS_OK;werr:// 关闭文件fclose(fp);// 删除文件unlink(tmpfile);redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));if (di) dictReleaseIterator(di);return REDIS_ERR;
}