详解 Redis 持久化之掌握 RDB ⽂件的格式,学习如何制作数据库镜像
创始人
2024-03-25 16:02:20
0

本文带大家了解一下 Redis 数据一种持久化方式 RDB 的实现。包括 Redis 内存快照 RDB ⽂件的创建时机以及⽣成⽅法。可以让你掌握 RDB ⽂件的格式,学习如何制作数据库镜像。

RDB 创建的入口函数

Redis 创建 RDB 文件的函数有三个,分别是 rdbSave, rdbSaveBackground, rdbSaveToSlavesSockets 这三个函数。

rdbSave

rdbSave 是 Redis 在本地磁盘创建 RDB ⽂件的入口函数。它对应了 Redis 的 save 命令,会在 save 命令的实现函数 saveCommand 中被调用,这个命令是使用主线程执行的,会阻塞其他命令的执行。rdbSave 函数最终会调用 rdbSaveRio 函数来实际创建RDB⽂件。

rdbSaveBackground

rdbSaveBackground 是 Redis 使⽤⼦进程方式在本地磁盘创建 RDB ⽂件的入口函数。它对应了 Redis 的 bgsave 命令,会在 bgsave 命令的实现函数 bgsaveCommand 中被调⽤。这个函数会调⽤ fork 创建 ⼀个⼦进程,让⼦进程调用 rdbSave 函数来创建 RDB ⽂件,而主线程本⾝可以继续处理客户端请求。

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {...if ((childpid = fork()) == 0) {// 子进程执行方法...// 调用 rdbSave 创建 RDB 文件retval = rdbSave(filename,rsi);...// 子进程退出exitFromChild((retval == C_OK) ? 0 : 1);} else {/* Parent */// 父进程也就是主线程执行方法...return C_OK;}return C_OK; /* unreached */
}

rdbSaveToSlavesSockets

rdbSaveToSlavesSockets 函数是 Redis 采用不落盘方式传输 RDB 文件进行主从复制时,创建 RDB文件的入口函数。

与 rdbSaveBackground 函数类似,rdbSaveToSlavesSockets 也是通过创建子进程,让子进程创建 RDB 文件。与 rdbSaveBackground 不同的是,rdbSaveToSlavesSockets 是通过网络以字节流的形式直接发送 RDB 文件的二进制文件数据给从节点。

RDB 创建时机

从上面的分析中我们知道 RDB 文件创建的三个时机分别是执行 save 命令、执行 bgsave 命令和进行主从复制。除了这几个时机还有哪些地方会触发 RDB 文件的创建呢?接下来我们将分析一下其他的创建时机。

rdbSave

通过查找 rdbSave 函数的调用,我们发现在 db.c 文件中的 flushallCommand 函数和 server.c 文件中的 prepareForShutdown 函数会调用 rdbSave 函数,也就是说在 Redis 执行 flushall 命令或 Redis 正常关闭时会创建 RDB 文件。

rdbSaveBackground

通过查找rdbSaveBackground 函数的调用,我们发现在 replication.c 中的 startBgsaveForReplication 函数和 server.c 文件中的 serverCron 函数会调用 rdbSaveBackground 函数,也就是说在主从复制以及定时任务按周期会调用 rdbSaveBackground 来创建 RDB 文件。

serverCron 函数会在下面两种情况下调用 rdbSaveBackground 生成 RDB 文件。

  • 满足配置的定时生成 RDB 文件的配置时。

  • bgsave 因为 AOF 重写导致 bgsave 被迫推迟时。

可见 RDB 文件只是周期性的保存某一时刻的数据。

rdbSaveToSlavesSockets

过查找rdbSaveToSlavesSockets 函数的调用,我们发现只有在 replication.c 中的 startBgsaveForReplication 函数会被调用,而 startBgsaveForReplication 函数被 replication.c ⽂件中的 syncCommand 函数和 replicationCron 函数调⽤,也就是说 Redis 执行主从复制命令以及周期性检测主从复制状态时会触发 RDB ⽣成。为了让从节点能够识别⽤来同步数据的 RDB 内容,rdbSaveToSlavesSockets 函数调⽤ rdbSaveRioWithEOFMark 函数在 RDB ⼆进制数据的前后加上了标识字符串,我们来看下代码:

#define RDB_EOF_MARK_SIZE 40int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {char eofmark[RDB_EOF_MARK_SIZE];// 生成随机成 40 字节的 16 进制字符串,保存在 eofmark 中getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);if (error) *error = 0;// 写入 $EOF:if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;// 写入 eofmarkif (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;// 写入 \r\nif (rioWrite(rdb,"\r\n",2) == 0) goto werr;// 写入 rdb 中的数据if (rdbSaveRio(rdb,error,RDB_SAVE_NONE,rsi) == C_ERR) goto werr;// 再次写入 eofmarkif (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;return C_OK;werr: /* Write error. *//* Set 'error' only if not already set by rdbSaveRio() call. */if (error && *error == 0) *error = errno;return C_ERR;
}

新增的标识字符串如下图所示:

好了到这里我们找到了所有 RDB 创建的时机,下面这张图展示了函数的调用关系。

RDB 文件

一个 RDB 文件是主要由三部分组成的。

  • 文件头: 这部分内容保存了Redis 的魔数、RDB 版本、Redis 版本、RDB ⽂件创建时间、键值对占⽤的内存大小等信息。

  • 文件数据: 这部分保存了 Redis 数据库实际的所有键值对。

  • 文件尾: 这部分保存了 RDB ⽂件的结束标识符,以及整个⽂件的校验值。用于校验文件是否被篡改。 RDB 文件组成如下图所示:

​真正创建 RDB 文件的函数是 rdbSaveRio,下面我们通过 rdbSaveRio 函数分别看文件的这三部分具体实现。

文件头

rdbSaveRio 首先会将魔数写入 RDB文件,当在 RDB ⽂件头中写⼊魔数后,rdbSaveRio 函数紧接着会调⽤ rdbSaveInfoAuxFields 函数将和 Redis server 相关的⼀些属性信息写⼊ RDB ⽂件头。

...// 生成魔数 magicsnprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);// 将魔数写到 RDB 中if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;// 写入属性信息if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
...

文件数据

Redis Server 中会有多个数据库,rdbSaveRio 会遍历所有的数据库,并将里面的数据写入到 RDB 文件中。我们看一下代码实现:

...for (j = 0; j < server.dbnum; j++) {...// 写入 SELECTDB 操作符if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;// 写入数据库编号if (rdbSaveLen(rdb,j) == -1) goto werr;uint64_t db_size, expires_size;// 获取全局哈希表大小db_size = dictSize(db->dict);// 获取过期键哈希表大小expires_size = dictSize(db->expires);// 写入 RESIZEDB 操作符if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;// 写入全局哈希表大小if (rdbSaveLen(rdb,db_size) == -1) goto werr;// 写入过期键哈希表大小if (rdbSaveLen(rdb,expires_size) == -1) goto werr;// 遍历所有的键值对while((de = dictNext(di)) != NULL) {// 获取键sds keystr = dictGetKey(de);// 获取值对象robj key, *o = dictGetVal(de);long long expire;// 将 key 从 sds 类型转换为 robjinitStaticStringObject(key,keystr);// 获取键的过期时间expire = getExpire(db,&key);// 将键、值以及过期时间写入 RDB 文件if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;/* When this RDB is produced as part of an AOF rewrite, move* accumulated diff from parent to child while rewriting in* order to have a smaller final write. */if (flags & RDB_SAVE_AOF_PREAMBLE &&rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES){processed = rdb->processed_bytes;aofReadDiffFromParent();}}dictReleaseIterator(di);...}
...

通过上面的代码我们可以看到,rdbSaveRio 函数会先将 SELECTDB 操作码和对应的数据库编号写⼊ RDB ⽂件中,这样方便解析时知道下面的数据是哪个数据库的。然后 rdbSaveRio 函数会写⼊ RESIZEDB 操作码,⽤来标识全局哈希表和过期 key 哈希表中键值对数量。最后 rdbSaveRio 函数会遍历当前数据库的所有键值对,把键、值以及过期时间写入 RDB 文件中。到这文件数据就写入完成了。

文件尾

写入文件尾的操作比较简单,主要写入两部分内容,一个是文件结束的操作码标识,另一个是校验码。下面我们看一下代码中是如何实现的:

    ...// 写入结束操作码if (rdbSaveType(rdb,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. */cksum = rdb->cksum;memrev64ifbe(&cksum);// 写入校验码if (rioWrite(rdb,&cksum,8) == 0) goto werr;...

好了,到这我们就分析完 Redis 中 RDB 文件的创建过程。

小结

本文主要介绍了内存快照文件 RDB 的三个入口函数以及创建 RDB 文件的时机还有 RDB 文件的生成过程。RDB 文件保存了 Redis 某一时刻所有的键值对,以及这些键值对的类型、大小、过期时间等信息。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...