0%

redis的RDB简介

RDB简介

RDB的全称是Redis Database File,redis的RDB刷盘为异步刷盘,配置N秒内产生了M次更新操作则触发刷盘。
redis内部会每100ms进行一次检测。判断是否需要刷盘。

刷盘的配置:

save 900 1   //表示900s内有一次修改
save 300 10  //表示300s内有10次修改
save 60 10000 //表示60s内有10000次修改

满足上述条件后就会通过BGSAVE命令刷盘

刷盘有两个命令,SAVE和BGSAVE(Background Save)

  1. redis的save命令会导致阻塞,无法执行其他的命令。
  2. redis的bgsave命令会开启新的进程来进行备份,由于新的进程的数据空间会和父进程的独立,故相当于刷盘的数据是这一刻的快照。
  3. bgsave和bgrewriteaof命令都是生成子进程处理刷盘,两个命令不会同时运行。
  4. 如果bgrewriteaof正在运行的时候执行bgsave命令,该命令会被拒绝。如果bgsave正在运行的时候执行bgrewriteaof命令,该命令会被阻塞到bgsave命令运行完毕后执行。
  5. redis的rdb文件在执行redis-serverD目录位置下,可以用这个命令来寻找
    find / -name dump.rdb

RDB文件说明

简要分析rdb文件:
REDIS 表示这个文件是redis的rdb文件。
0006 表示版本号
379 表示下一个字节是数据库编号
0 表示是第0个db
0 表示保存的类型的REDIS_DB_TYPE_STRING
003 表示下一个字符串长度为3个字符
msg 表示是key的字符
005 表示下一个字符串长度为5个字符
hello 表示value的字符
337 表示的是EOF常量
剩余的8个字符表示的是文件校验,用于判断文件是否完整

RDB过程代码分析

int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;

    // 如果 BGSAVE 已经在执行,那么出错
    if (server.rdb_child_pid != -1) return REDIS_ERR;

    // 记录 BGSAVE 执行前的数据库被修改次数
    server.dirty_before_bgsave = server.dirty;

    // 最近一次尝试执行 BGSAVE 的时间
    server.lastbgsave_try = time(NULL);

    // fork() 开始前的时间,记录 fork() 返回耗时用
    start = ustime();

    if ((childpid = fork()) == 0) {
        int retval;

        /* Child */

        // 关闭监听的fd
        closeListeningSockets(0);

        // 设置进程的标题,方便识别
        redisSetProcTitle("redis-rdb-bgsave");

        // 执行保存操作
        retval = rdbSave(filename);

        // 向父进程发送信号
        exitFromChild((retval == REDIS_OK) ? 0 : 1);

    } else {

        /* Parent */

        // 计算 fork() 执行的时间
        server.stat_fork_time = ustime()-start;

        // 打印 BGSAVE 开始的日志
        redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);

        // 记录数据库开始 BGSAVE 的时间
        server.rdb_save_time_start = time(NULL);

        // 记录负责执行 BGSAVE 的子进程 ID
        server.rdb_child_pid = childpid;

        // 关闭自动 rehash
        updateDictResizePolicy();

        return REDIS_OK;
    }

    return REDIS_OK; /* unreached */
}

说明:

  1. 避免出现子进程对父进程的socket产生并发的竞争情况,出现客户端同时连接到子进程和父进程的情况,fork后的第一步,子进程就要close父进程的监听fd,包括,监听的socket,监听本地连接的socket,集群的socket。

  2. 设置静态变量中的进程标题。

  3. rdbSave函数则是主要的功能函数

    int rdbSave(char *filename) {

     // 创建临时文件
     fp = fopen(tmpfile,"w");
    
     // 初始化 I/O,变量rdb为rdb文件的处理状态
     rioInitWithFile(&rdb,fp);
    
     // 写入 RDB 版本号
     snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
     rdbWriteRaw(&rdb,magic,9) == -1;
    
     // 遍历所有数据库
     for (j = 0; j < server.dbnum; j++) {
    
         //遍历每个数据库,将key,value,超时时间写入文件中
     }
     di = NULL; /* So that we don't release it again on error. */
    
     /* 
      * 写入 EOF 代码
      */
     rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF);
    
    
     // 冲洗缓存,确保数据已写入磁盘
     if (fflush(fp) == EOF) goto werr;
     if (fsync(fileno(fp)) == -1) goto werr;
     if (fclose(fp) == EOF) goto werr;
    
     /* 
      * 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。
      */
     (rename(tmpfile,filename);
    
    
     // 写入完成,打印日志
     redisLog(REDIS_NOTICE,"DB saved on disk");
    
     // 清零数据库脏状态
     server.dirty = 0;
    
     // 记录最后一次完成 SAVE 的时间
     server.lastsave = time(NULL);
    
     // 记录最后一次执行 SAVE 的状态
     server.lastbgsave_status = REDIS_OK;
    
     return REDIS_OK;
    

    }