美文网首页JavaJava 程序员
Redis 源码分析——RBD 持久化,面试官被我讲懵了

Redis 源码分析——RBD 持久化,面试官被我讲懵了

作者: 马小莫QAQ | 来源:发表于2022-03-10 17:30 被阅读0次

    原理

    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 结构图

    图例:

    1. 粉色框中字段为固定字符串
    2. 绿色框中字段为整数
    3. 紫色框中是拓展的数据
    4. 黄色框中value类型是一个枚举的常量
    5. 蓝色框中是实际存储的键值对数据

    作者:心城以北
    链接:https://juejin.cn/post/7072757775247343623
    来源:稀土掘金

    相关文章

      网友评论

        本文标题:Redis 源码分析——RBD 持久化,面试官被我讲懵了

        本文链接:https://www.haomeiwen.com/subject/yexqdrtx.html