Redis 持久化与故障恢复之rdb

article/2025/8/29 17:31:32

一、摘要

         老生常谈一下吧,redis持久化分为rdb和aof两种模式,本篇先说一说rdb模式吧,共分为三部分:1:如何触发rdb持久化, 2:rdb持久化源码, 3:rdb文件解析。

        ps:本文基于redis7分析。

二、如何触发rdb持久化

  1:通过save关键字在redis.conf文件配置触发条件

# save <seconds> <changes> [<seconds> <changes> ...]

save 3600 1 600 100 60 3000

        上述配置表示如果满足每隔3600s内有1个key发生变化,每隔600s内有100个key发生变化,每隔60s内有3000个key发生变化三个条件中的一个,就会触发rdb持久化。

        ps:触发后执行过程与bgsave命令一样

  2:在cli执行save或bgsave命令

       save表示同步执行rdb持久化,会阻塞其它客户端命令的响应;

       bgsve表示异步处理rdb持久化,不会阻塞。

  3:bgsave执行流程如下图:

            

 可以概括为:触发rdb持久化后,redis主进程会fork一个子进程出来,子进程会将内存数据dump到临时的rdb快照文件中,在完成rdb快照文件的生成之后,就替换(通过rename系统函数完成替换)之前的旧的快照文件dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照。

三、rdb持久化源码

    代码核心逻辑在rdb.c文件中,其中核心函数是rdbSaveBackground。

    通过对rdb持久化触发方式的分析,可知有两种代码路径进入rdbSaveBackground函数。

   1:redis.conf 的save配置

//server.c
int main(int argc, char **argv) {
```initServer();
``
}
void initServer(void) {
```/* Create the timer callback, this is our way to process many background* operations incrementally, like clients timeout, eviction of unaccessed* expired keys and so forth. *///将定时任务添加到reactor的时间事件中去,1s一次if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {serverPanic("Can't create event loop timers.");exit(1);}
```
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
```
/* Check if a background saving or AOF rewrite in progress terminated. */if (hasActiveChildProcess() || ldbPendingChildren()){//如果有正在处理的rdb持久化或aof持久化,则不执行,仅仅检查run_with_period(1000) receiveChildInfo();checkChildrenDone();} else {/* If there is not a background saving/rewrite in progress check if* we have to save/rewrite now. */for (j = 0; j < server.saveparamslen; j++) {//这里的server.saveparams就是save关键字的配置,满足其中一个就执行rdbSaveBackgroundstruct saveparam *sp = server.saveparams+j;/* Save if we reached the given amount of changes,* the given amount of seconds, and if the latest bgsave was* successful or if, in case of an error, at least* CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed. */if (server.dirty >= sp->changes &&server.unixtime-server.lastsave > sp->seconds &&(server.unixtime-server.lastbgsave_try >CONFIG_BGSAVE_RETRY_DELAY ||server.lastbgsave_status == C_OK)){serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",sp->changes, (int)sp->seconds);rdbSaveInfo rsi, *rsiptr;rsiptr = rdbPopulateSaveInfo(&rsi);rdbSaveBackground(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE);break;}}
```
}

2:bgsave 和save

      redis收到bgsave的命令,会执行bgsaveCommand函数;

     redis收到save的命令,会执行saveCommand函数。

void saveCommand(client *c) {if (server.child_type == CHILD_TYPE_RDB) {addReplyError(c,"Background save already in progress");return;}server.stat_rdb_saves++;rdbSaveInfo rsi, *rsiptr;rsiptr = rdbPopulateSaveInfo(&rsi);//执行保存内存数据到rdb文件if (rdbSave(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE) == C_OK) {addReply(c,shared.ok);} else {addReplyErrorObject(c,shared.err);}
}/* BGSAVE [SCHEDULE] */
void bgsaveCommand(client *c) {```if (server.child_type == CHILD_TYPE_RDB) {addReplyError(c,"Background save already in progress");} else if (hasActiveChildProcess() || server.in_exec) {//有活跃的子进程就不会执行rdbSaveBackgroundif (schedule || server.in_exec) {server.rdb_bgsave_scheduled = 1;addReplyStatus(c,"Background saving scheduled");} else {addReplyError(c,"Another child process is active (AOF?): can't BGSAVE right now. ""Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever ""possible.");}} else if (rdbSaveBackground(SLAVE_REQ_NONE,server.rdb_filename,rsiptr,RDBFLAGS_NONE) == C_OK) {addReplyStatus(c,"Background saving started");} else {addReplyErrorObject(c,shared.err);}
}
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
```if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {int retval;/* Child */redisSetProcTitle("redis-rdb-bgsave");redisSetCpuAffinity(server.bgsave_cpulist);//执行保存内存数据到rdb文件retval = rdbSave(req, filename,rsi,rdbflags);if (retval == C_OK) {//如果重新生成rdb文件成功,则通知主进程sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB");}exitFromChild((retval == C_OK) ? 0 : 1);}
```
}

3:概括代码函数调用过程

   

 4:rdbsave

/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
int rdbSave(int req, char *filename, rdbSaveInfo *rsi, int rdbflags) {
```//创建临时rdb文件snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());fp = fopen(tmpfile,"w");//将内存数据放入临时rdb文件if (rdbSaveRio(req,&rdb,&error,rdbflags,rsi) == C_ERR) {errno = error;err_op = "rdbSaveRio";goto werr;}/* Make sure data will not remain on the OS's output buffers */if (fflush(fp)) { err_op = "fflush"; goto werr; }if (fsync(fileno(fp))) { err_op = "fsync"; goto werr; }if (fclose(fp)) { fp = NULL; err_op = "fclose"; goto werr; }//重命名临时文件为正式rdb文件if (rename(tmpfile,filename) == -1) {...}```
}
int rdbSaveRio(int req, rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
```//定义rdb文件中check_sum部分的生成函数if (server.rdb_checksum)rdb->update_cksum = rioGenericUpdateChecksum;//定义rdb文件开头部分,REDIS+db_versionsnprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);//保存每个db的数据/* save all databases, skip this if we're in functions-only mode */if (!(req & SLAVE_REQ_RDB_EXCLUDE_DATA)) {for (j = 0; j < server.dbnum; j++) {if (rdbSaveDb(rdb, j, rdbflags, &key_counter) == -1) goto werr;}}//生成check_sum,并追加到rdb文件最后cksum = rdb->cksum;memrev64ifbe(&cksum);if (rioWrite(rdb,&cksum,8) == 0) goto werr;
```
}
//保存每个db的数据
ssize_t rdbSaveDb(rio *rdb, int dbid, int rdbflags, long *key_counter) {
```/* Write the SELECT DB opcode *///写入rdb文件SELECTDB标志位,表示这里开始要进入某个db了if ((res = rdbSaveType(rdb,RDB_OPCODE_SELECTDB)) < 0) goto werr;written += res;//写入rdb文件当前db的索引号,表示这里开始的数据是某个db的数据if ((res = rdbSaveLen(rdb, dbid)) < 0) goto werr;written += res;//写key val /* Iterate this DB writing every entry */while((de = dictNext(di)) != NULL) {sds keystr = dictGetKey(de);robj key, *o = dictGetVal(de);long long expire;size_t rdb_bytes_before_key = rdb->processed_bytes;expire = getExpire(db,&key);if ((res = rdbSaveKeyValuePair(rdb, &key, o, expire, dbid)) < 0) goto werr;}
```
}
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, int dbid) {/* Save the expire time */if (expiretime != -1) {//有超时时间的话,保存RDB_OPCODE_EXPIRETIME_MS标志位if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;//保存过期时间戳,毫秒if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;}//依次保存数据类型,key val三个内容/* Save type, key, value */if (rdbSaveObjectType(rdb,val) == -1) return -1;if (rdbSaveStringObject(rdb,key) == -1) return -1;if (rdbSaveObject(rdb,val,key,dbid) == -1) return -1; }

  ps:注意下rdbSaveRio之后的函数调用,其中可以一窥rdb文件结构。

四、rdb文件结构

     1:一个完整RDB文件所包含的各个部分如下图,代码见rdbSaveRio函数

   

     1)RDB文件的最开头是REDIS部分,这个部分的长度为5字节,保存 “REDIS”五个字符。通过这五个字符,程序可以在载入文件时,快速 检查所载入的文件是否RDB文件。

     2)db_version长度为4字节,它的值是一个字符串表示的整数,这个整 数记录了RDB文件的版本号,比如redis7的该值就是11,见rdbSaveRio函数的RDB_VERSION

    3)db_content部分包含着零个或多个数据库,以及各个数据库中的键值对数据。

    4)EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结 束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了。

   5)check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDISdb_versiondatabasesEOF四个部分的内 容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出 的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件 是否有出错或者损坏的情况出现。

    2:db_content内部结构如下图,代码见rdbSaveDb函数

  

   1)SELECTDB常量的长度为1字节,当读入程序遇到这个值的时候, 它知道接下来要读入的将是一个数据库索引号。

   2)db_number保存着一个数据库索引号,根据号码的大小不同,这个部 分的长度可以是1字节、2字节或者5字节。当程序读入db_number部分之 后,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切 换,使得之后读入的键值对可以载入到正确的数据库中。

  3)key_value_pairs部分保存了数据库中的所有键值对数据,如果键值 对带有过期时间,那么过期时间也会和键值对保存在一起。根据键值对 的数量、类型、内容以及是否有过期时间等条件的不同, key_value_pairs部分的长度也会有所不同。

    3:key_value_pairs结构如下图,代码见rdbSaveKeyValuePair函数   

      [1]   :无过期时间的结构

              

       [2]:有过期时间的结构

             

      1)EXPIRETIME_MS常量的长度为1字节,它告知读入程序,接下来 要读入的将是一个以毫秒为单位的过期时间。

      2)ms是一个8字节长的带符号整数,记录着一个以毫秒为单位的 UNIX时间戳,这个时间戳就是键值对的过期时间,这样在加载rdb文件时如果ms过期就不加载该值了。

      3)TYPE记录了value的类型,长度为1字节,值可以是以下常量的其中 一个:

  • REDIS_RDB_TYPE_STRING
  • REDIS_RDB_TYPE_LIST    
  • REDIS_RDB_TYPE_SET
  • REDIS_RDB_TYPE_HASH_ZIPLIST
  • REDIS_RDB_TYPE_ZSET_ZIPLIST
  • REDIS_RDB_TYPE_SET_INTSET
  • REDIS_RDB_TYPE_HASH
  • REDIS_RDB_TYPE_LIST_ZIPLIST
  • REDIS_RDB_TYPE_ZSET
    这样redis在加载value时就可以根据TYPE去解析了
    4) key 总是一个字符串对象,它的编码方式和 REDIS_RDB_TYPE_STRING 类型的 value 一样。根据内容长度的不同, key 的长度也会有所不同。
    5)value就是具体的数据内容了, 根据 TYPE 类型的不同,以及保存内容长度的不同,保存 value 的结 构和长度也会有所不同。

http://chatgpt.dhexx.cn/article/ccmjfIPw.shtml

相关文章

【Redis】Redis持久化之RDB详解(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化。文章内容兼具广度深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于知名金融公司…

redis持久化之rdb

redis持久化之rdb RDB相关配置rdb快照的触发条件rdb快照过程rdb文件结构rdb和aof文件的加载顺序 Redis的RDB持久化方式是指将某个时刻的内存数据进行快照&#xff0c;生成.rdb文件可以用于恢复快照时刻redis的内存数据。 RDB相关配置 我们可以在redis.conf中进行rdb相关的配置…

redis之AOF和RDB持久化

写在前面 redis数据存储在内存&#xff0c;为了避免服务器重启或者是宕机导致数据全部丢失&#xff0c;提供了数据持久化机制&#xff0c;有AOF(Append Only File)和RDB&#xff0c;接下来我们分别看下。 1&#xff1a;AOF 如下是我本地环境生成aof文件&#xff1a; 1.1&am…

AOFRDB

序言 主要用于查看不同备份模式可以调优的一些方式. RDB: Redis服务器在规定时间内将内存中的数据保存到指定路径的文件中,服务器重启的时候直接将RDB文件读取到内存中AOF:Redis服务器以日志的形式追加所有的REDIS操作,服务器重启的时候读取AOF文件依次执行一遍. 关于RDB最主…

RDB持久化触发机制

RDB持久化 触发机制 手动触发 手动触发分别对应save和bgsave命令&#xff1a; save命令&#xff1a;同步&#xff0c;在主线程中保存快照&#xff1b;阻塞当前Redis服务器&#xff0c;直到RDB过程完成为止&#xff0c;对于内存比较大的实例会造成长时间阻塞&#xff0c;线上…

Redis RDB 和 AOF

导读 RDB 和 AOF 对比&#xff1a; 持久化方式选择 如果对数据安全性要求极高&#xff0c;应该同时使用两种持久化方式。 如果可以承受若干时间内的数据丢失&#xff0c;可以只使用 RDB 持久化。 不建议只使用 AOF 持久化&#xff0c;因为定时生成 RDB 快照&#xff08;s…

java读取rdb_剖析Redis RDB文件

通过redis-cli中执行save或者bgsave可以得到RDB文件(文件名由配置文件中dbfilename指定&#xff0c;例如dbfilename "dump.rdb")&#xff0c;这个文件包含Redis实例中全量的数据&#xff0c;那么dump.rdb的文件格式大概是什么样的呢&#xff1f; RDB文件定义 获取RDB…

Redis RDB文件解析

Redis RDB是什么 Redis rdb是Redis快照存储持久化方式&#xff0c;具体就是将Redis某一时刻的内存数据保存到硬盘的文件当中&#xff0c;默认保存的文件名为dump.rdb&#xff0c;而在Redis服务器启动时&#xff0c;会重新加载dump.rdb文件的数据到内存当中恢复数据&#xff0c…

RDB 持久化详解

文章目录 持久化的执行SAVE&#xff1a;阻塞服务器并创建RDB文件BGSAVE&#xff1a;以非阻塞方式创建RDB文件通过配置文件自动创建RDB文件查看最近持久化时间 RDB优化配置1. save2. stop-write-on-bgsave-error3. rdbcompression4. rdbchecksum5. sanitize-dump-payload6. dbfi…

RDB持久化

RDB持久化 一、RDB快照实现二、快照用法三、执行快照时的数据修改四、RDB 和 AOF 合体 一、RDB快照实现 Redis 是内存数据库&#xff0c;但是它为数据的持久化提供了两个技术。 分别是「 AOF 日志和 RDB 快照」。 这两种技术都会用各用一个日志文件来记录信息&#xff0c;但…

RDB底层原理

Redis服务器自动保存功能的实现原理及文件中的各个组成部分 因为Redis是内存数据库&#xff0c;它将自己的数据库状态储存在内存里 面&#xff0c;所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面&#xff0c; 那么一旦服务器进程退出&#xff0c;服务器中的数据库…

Redis持久化原理(RDB)

在上一篇文章中&#xff0c;介绍了Redis的内存模型很重要&#xff0c;从这篇文章开始&#xff0c;将依次介绍Redis高可用相关的知识——持久化、复制(及读写分离)、哨兵、以及集群。 本文将先说明上述几种技术分别解决了Redis高可用的什么问题&#xff1b;然后详细介绍Redis的…

运行程序时出现 应用程序无法启动,因为应用程序的并行配置不正确

最近发布给客户的程序中&#xff0c;双击exe程序出现“应用程序无法启动&#xff0c;因为应用程序的并行配置不正确”&#xff0c;下面提示使用sxstrace跟踪调试应用程序运行时需要的动态库的版本和路径。 于是有以下操作&#xff1a; 步骤&#xff1a; 1.利用管理员身份运行命…

谷歌浏览器出现应用程序无法启动,因为应用程序的并行配置不正确....(亲测完美解决)

电脑开机 完整报错提示为&#xff1a; 应用程序无法启动&#xff0c;因为应用程序的并行配置不正确。有关详细信息&#xff0c;请参阅应用程序事件日志&#xff0c;或使用命令行 sxstrace.exe 工具。 刚开始也在网上找了N久的解决方案&#xff08;如&#xff1a;启动windws Mod…

成功解决“tesseral应用程序无法打开,因为运行程序并行配置不正确”

目前网上流传的两种方法1是启动Windows Modules Installer并重启 但我的并没有处于禁用状态。 第二种是查看日志安装对应库&#xff0c;比较麻烦。 无意中发现第三种方式&#xff0c;操作更为简单。 我的tesseral版本是7.0.6&#xff0c;初次打开会提示如题报错&#xff0c;…

chrome 应用程序无法启动,因为应用程序的并行配置不正确

方法一&#xff1a; 开始 - 运行&#xff08;输入services.msc&#xff09;- 确定或回车&#xff0c;打开&#xff1a;服务&#xff08;本地&#xff09;&#xff1b; 我们在服务&#xff08;本地&#xff09;窗口找到&#xff1a;Windows Modules Installer服务&#…

exe应用程序无法启动,因为应用程序的并行配置不正确

问题&#xff1a;exe应用程序无法启动&#xff0c;因为应用程序的并行配置不正确。有关详细信息&#xff0c;请参阅应用程序事件日志&#xff0c;或使用命令行 sxstrace.exe 工具。 原因查找&#xff1a; 1&#xff09;开始→所有程序→附件→右键命令提示符→以管理员身份运…

STM32CubeMx开发之路—在线升级OTA

文章目录 运行环境简介基础知识(1/4)STM32中的程序在哪儿?进行分区总体流程图 BootLoader程序流程图分析程序编写和分析 App程序流程图分析程序编写和分析Ymodem协议代码分析 结果验证代码的下载BootLoader的下载App1的下载生成App2的.bin文件使用Xshell进行文件传输 总结源代…

基于FPGA的在线升级

基于FPGA的在线升级 在线升级的意义在线升级的策略整体框架总结参考文献结束语 在线升级的意义 首先什么是FPGA的在线升级&#xff1f; 所谓FPGA的在线升级其实就是不对FPGA进行常规意义下的下载程序&#xff0c;便可以通过一些手段更新FPGA中的程序&#xff0c;一般用到的工具…

MCU通过UART实现OTA在线升级流程

关注星标公众号&#xff0c;不错过精彩内容 素材来源 | 网络 OTA升级已经不是什么新鲜事&#xff0c;现在大多数物联网终端设备&#xff0c;基本具备这个功能。 今天以AT32为例给大家分享一下OTA升级的详细流程。 概述 空中下载技术OTA&#xff08;Over-the-Air Technology&…