redis7.0rdb源码解析

rdb机制

rdb(redis database)是redis持久化方法中的一种,通过一次性将redis内存中的数据全量快照落盘,达到持久化的目的。全量落盘就意味着操作比较重度,无法做到快速秒级间隔持久化,所以一般用来做数据定时备份,例如每天一次。rdb的实现方法分为阻塞和非阻塞,阻塞就是执行快照期间不执行其它业务逻辑保证数据一致性,非阻塞就是fork子进程使用fork时候的主进程数据来落盘同时主进程还能对外提供服务。rdb的触发方式也分为手动和自动,手动就是redis客户端调用save或者bgsave,自动就是通过配置文件的save字段配置触发条件以及别的例如主从复制、flushall、shutdown时触发。

save

int rdbSave(int req, 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;
    char *err_op;    /* For a detailed log */

    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        char *str_err = strerror(errno);
        char *cwdp = getcwd(cwd,MAXPATHLEN);
        serverLog(LL_WARNING,
            "Failed opening the temp RDB file %s (in server root dir %s) "
            "for saving: %s",
            tmpfile,
            cwdp ? cwdp : "unknown",
            str_err);
        return C_ERR;
    }

    rioInitWithFile(&rdb,fp);
    startSaving(RDBFLAGS_NONE);

    if (server.rdb_save_incremental_fsync)
        rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);

    if (rdbSaveRio(req,&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
        errno = error;
        err_op = "rdbSaveRio";
        goto werr;
    }

    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp)) { err_op = "fflush"; goto werr; }
    if (fsync(fileno(fp))) { err_op = "fsync"; goto werr; }
    if (fclose(fp)) { fp = NULL; err_op = "fclose"; 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 *str_err = strerror(errno);
        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",
            str_err);
        unlink(tmpfile);
        stopSaving(0);
        return C_ERR;
    }
    if (fsyncFileDir(filename) == -1) { err_op = "fsyncFileDir"; goto werr; }

    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): %s", err_op, strerror(errno));
    if (fp) fclose(fp);
    unlink(tmpfile);
    stopSaving(0);
    return C_ERR;
}
  • 打开一个rdb临时文件temp-<pid>.rdb
  • startSaving,给事件模块发送一个开始rdb事件
  • rdb_save_incremental_fsync标记如果为true,rdb过程中每产生30MB数据就自动调用fsync落盘
  • rdbSaveRio开始执行rdb逻辑:
  • rdbWriteRaw写入rdb版本号,rdbSaveInfoAuxFields写入一些辅助字段信息,rdbSaveModulesAux遍历redis注册的模块写入模块的头辅助字段信息,rdbSaveFunctions遍历所有注册的lua函数写入rdb,遍历dbnum的数据库执行rdbSaveDb保存所有数据库数据(遍历每个key执行对应数据压缩保存),rdbSaveModulesAux遍历redis注册的模块写入模块的尾辅助字段信息,写入RDB_OPCODE_EOF结束标记,写入校验号

  • fflush将文件流写入内核缓冲区
  • fsync将文件内容立刻写入磁盘
  • fclose关闭文件
  • rename重命名临时文件为正式rdb文件
  • fsyncFileDir同步目录数据到磁盘
  • rdb结束

bgsave

int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi) {
    pid_t childpid;

    if (hasActiveChildProcess()) return C_ERR;
    server.stat_rdb_saves++;

    server.dirty_before_bgsave = server.dirty;
    server.lastbgsave_try = time(NULL);

    if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
        int retval;

        /* Child */
        redisSetProcTitle("redis-rdb-bgsave");
        redisSetCpuAffinity(server.bgsave_cpulist);
        retval = rdbSave(req, filename,rsi);
        if (retval == C_OK) {
            sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB");
        }
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent */
        if (childpid == -1) {
            server.lastbgsave_status = C_ERR;
            serverLog(LL_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return C_ERR;
        }
        serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        return C_OK;
    }
    return C_OK; /* unreached */
}
  • hasActiveChildProcess判断是否存在其它子进程,存在就不执行rdb
  • 调用fork,子进程开始rdb:
  • redisSetProcTitle设置子进程名:redis-rdb-bgsave
  • redisSetCpuAffinity设置cpu亲和性,将子进程绑定在某个cpu核心执行
  • rdbSave开始执行rdb,分析逻辑见上面
  • sendChildCowInfo给父进程管道发送rdb结束消息
  • exitFromChild退出子进程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值