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 (失败)