redis源码分析之RDB持久化

时间:2021-04-08 09:23:20

redis持久化有两种模式,RDB和AOF,系统会在时间事件(10ms)调用函数serverCron检查是否需要保存RDB或AOF。


一:RDB

 1:触发RDB执行条件:

    a:触发RDB执行由一组条件,即配置文件中的:

save 900 1
save 300 10
save 60 10000

解析配置文件后,保存到server.saveparams变量数组中,数组每个单元为saveparam结构体,保存配置文件中的值

saveparams变量的结构体如下:

struct saveparam {
    time_t seconds; // 间隔秒数  argv[1]
    int changes;    // 变化的数量  argv[2]
};

另:server.saveparamslen保存saveparams数组的长度,即条件的数量



b:serverCron每次执行时会循环server.saveparams数组,比较server.dirty >= saveparam.changes && server.unixtime - server.lastsave > saveparam.seconds,条件为真时调用rdbSaveBackground(server.rdb_filename)开始保存RDB

     说明:

           server.unixtime:当前系统时间戳

           server.lastsave:上次RDB保存成功的时间戳

          server.dirty:上次RDB保存成功后修改的键数量    

          server.rdb_filename:为RDB文件名


2:RDB执行过程:

     rdbSaveBackground(server.rdb_filename)会fork一个子进程执行RDB保存工作,不阻塞主进程。

     a:子进程:

          1:关闭两种IO,server.ipfd(网络IO)和server.sofd(本地IO),因为子进程不提供redis服务

          2:调用rdbSave(server.rdb_filename)开始保存RDB文件,RDB文件格式参见   redis源码分析之RDB文件格式

                 1:写入方式打开temp-pid.rdb临时文件(pid为运行保存RDB进程的进程pid)

                 2:初始化rio,即redis io操作,即向内核申请内存,避免多次调用write

                 3:遍历redis所有DB(0-15),保存所有的键值对到上述的redis io中,

                 4:RDB数据写入完毕,刷新redis io中的数据到文件中

                 5:执行rename操作,原子替换tmp-pid.rdb为 server.rdb_filename文件(即RDB保存的文件名)

                 6:重置修改的键数量:server.dirty = 0

                        更新最近一次RDB成功保存的时间:server.lastsave = time(NULL)

                        更新最近一次RDB保存的壮态:server.lastbgsave_status = REDIS_OK (成功)

                 7:使用exitFromChild返回子进程执行结果,由父进程捕获( RDB保存成功,exitFromChild(0);失败exitFromChild(1) )

      

    b:父进程:

         1:RDB开始前修改key的数量:server.dirty_before_bgsave = server.dirty

         2:记录最后一次 fork 的时间:server.stat_fork_time

         3:记录RDB开始的时间: server.rdb_save_time_start

         4:记录子进程的 id:server.rdb_child_pid

         5:在执行时调用 updateDictResizePolicy 关闭对数据库的 rehash,避免 copy-on-write

                                                                  dictEnableResize 

              updateDictResizePolicy --->                                      ----> 设置dict_can_resize的值为1(开启)或者0(关闭)

                                                                  dictDisableResize

    

       上述5步完成后,父进程继续提供服务;子进程进行RDB保存,当子进程RDB保存完,执行exitFromChild退出子进程;然后父进程在下一次serverCron会检测到子进程退出。

           检测方法:

                     1:检测server.rdb_child_pid 或 server.aof_child_pid是否存在,存在则说明有子进程在RDB或AOF操作。

                     2:调用 wait3的不阻塞模式获取退出的子进程pid 。

                     3:检查退出的子进程pid == server.rdb_child_pid,则表示RDB完成

                     4:获取子进程退出的壮态(exitcode)和 子进程退出由于信号引进(bysignal)两个值

                     5:调用backgroundSaveDoneHandler(exitcode,bysignal)进入RDB收尾工作。

                     6:调用updateDictResizePolicy 开启对数据库的 rehash

            RDB收尾工作:

                   1:重置RDB子进程为默认值: server.rdb_child_pid = -1

                         更新RDB结束的时间:server.rdb_save_time_last  = time(NULL)-server.rdb_save_time_start

                         重置RDB开始执行时间为默认值:server.rdb_save_time_start = -1

                   2:检测RDB子进程是否正常退出,即保存成功:(!bysignal && exitcode == 0 )

                           更新RDB开始前修改key的数量:server.dirty = server.dirty - server.dirty_before_bgsave

                           更新最近一次RDB成功保存的时间:server.lastsave = time(NULL)

                           更新最近一次RDB保存的壮态:server.lastbgsave_status = REDIS_OK (成功)

                   3:检测RDB子进程是否异常退出,即保存失败:(!bysignal && exitcode != 0)

                          更新最近一次RDB保存的壮态 :server.lastbgsave_status = REDIS_ERR (失败)

                   4:其它情况则说明捕获到未知的子进程退出

                           删除RDB的临时文件(未知子进程可能导致还未来的及删除文件):temp-childPid.rdb

                           更新最近一次RDB保存的壮态 :server.lastbgsave_status = REDIS_ERR (失败)