Redis 源码分析——RBD 持久化,面试官被我讲懵了
2022-03-10 本文已影响0人
马小莫QAQ
原理
Redis 提供了 RDB 持久化功能,这个功能可以将 Redis 在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。
触发时机:手动触发、自动触发。
配置读取
redis.conf中 rdb 相关的配置如下:
rdbcompression
rdb 文件为了解约空间,支持压缩,要开启该功能
需要在配置文件中设置参数 rdbcompression (默认开启的),
当前开启参数后 redis 利用 lz 算法对
stop-writes-on-bgsave-error
为了保证数据的一致性,redis 默认开启该选项
dbfilename
rdb 文件名,默认 dump.rdb
save <seconds> <changes>
指明 rdb 触发机制,表示 seconds 秒改变 changes 触发
mac 系统 brew 安装 rdb 文件位置:
rdb 文件查看
od -c dump.rdb
➜ redis od -c dump.rdb
0000000 R E D I S 0 0 0 9 372 \t r e d i s
0000020 - v e r 005 6 . 0 . 9 372 \n r e d i
0000040 s - b i t s 300 @ 372 005 c t i m e 313
0000060 313 x ' b 372 \b u s e d - m e m 302 300
0000100 B 020 \0 372 \f a o f - p r e a m b l
0000120 e 300 \0 376 \0 373 001 \0 370 " \0 003 m s g 300
0000140 001 377 9 3 235 231 R 251 \ N
0000152
手动触发
Redis 可以执行 SAVE、BGSAVE进行手动触发 RDB 持久化操作。
- SAVE 命令会阻塞 Redis 服务进程,直到 RDB 文件常见完毕。
- BGSAVE 不会阻塞服务器进程,会创建一个子进程,子进程负责创建 RDB 文件。
RDB 文件的创建是由 rdb.c/rdbSave函数完成,具体代码如下:
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
int rdbSave(char *filename, rdbSaveInfo *rsi) {
char tmpfile[256];
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
FILE *fp = NULL;
rio rdb;
int error = 0;
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Failed opening the RDB file %s (in server root dir %s) "
"for saving: %s",
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
return C_ERR;
}
rioInitWithFile(&rdb,fp);
startSaving(RDBFLAGS_NONE);
if (server.rdb_save_incremental_fsync)
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
if (rdbSaveRio(&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
errno = error;
goto werr;
}
/* Make sure data will not remain on the OS's output buffers */
if (fflush(fp)) goto werr;
if (fsync(fileno(fp))) goto werr;
if (fclose(fp)) { fp = NULL; goto werr; }
fp = NULL;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
if (rename(tmpfile,filename) == -1) {
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Error moving temp DB file %s on the final "
"destination %s (in server root dir %s): %s",
tmpfile,
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
serverLog(LL_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
stopSaving(1);
return C_OK;
// 出错处理
werr:
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
if (fp) fclose(fp);
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
自动触发
Redis 支持用户通过设置服务器配置save 选项(在 redis.conf 中) ,让服务服务器每间隔一段事件自动执行一次 BGSAVE命令。
默认配置如下所示:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:
- 服务器在 900 秒之内,对数据库进行了至少 1 次修改。
- 服务器在 300 秒之内,对数据库进行了至少 10 次修改。
- 服务器在 60 秒之内,对数据库进行了至少 10000 次修改。
服务器中会根据 save 选项设置的保存条件,设置服务器状态的 redisServer 结构的 saveparams 属性
struct redisServer {
// 记录保存条件
struct saveparam *saveparams; /* Save points array for RDB */
}
// saveparam 定义
struct saveparam {
time_t seconds;
int changes;
};
总结
1、Redis 中提供了手动,自动两种方式来持久化数据,其实也是在性能和可靠性的折中处理。
2、自动持久化除了 saveparams数组之外,服务器状态还维持着一个dirty计数器,以及一个lastsave属性:
- dirty 计数器记录距离上一次成功执行 SAVE 命令或者 BGSAVE 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。
- lastsave 属性是一个UNIX时间戳,记录了服务器上一次成功执行 SAVE 命令或者 BGSAVE 命令的时间。
struct redisServer {
// 修改计数器
long long dirty; /* Changes to DB from the last save */
// 上次保存时间
time_t lastsave; /* Unix time of last successful save */
}
3、Redis 的服务器周期性操作函数 serverCron 默认每隔 100 毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查 save 选项所设置的保存条件是否已经满足,如果满足的话,就执行 BGSAVE 命令。
RDB 结构图
图例:
- 粉色框中字段为固定字符串
- 绿色框中字段为整数
- 紫色框中是拓展的数据
- 黄色框中value类型是一个枚举的常量
- 蓝色框中是实际存储的键值对数据
作者:心城以北
链接:https://juejin.cn/post/7072757775247343623
来源:稀土掘金