原理
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类型是一个枚举的常量
- 蓝色框中是实际存储的键值对数据