《redis设计与实现》-10 RDB持久化

一 序

     本章书上内容较大部分介绍了RDB文件的创建与载入、自动间隔性保存、RDB文件结构。所以本文分为两部分上边参照书上梳理,第二部分看bgsave的源码实现。

   Redis是内存型数据库,如果服务器进程退出,服务器中的数据状态也会消失不见,所以需要持久化功能。为了解决这个问题,Redis提供了两种持久化的机制:RDB和AOF。本篇主要整理RDB持久化的部分。

RDB持久化功能生成的RDB文件时一个经过压缩处理的二进制文件,通过该文件可以还原生成EDB文件的数据库状态,如下图所示

二 RDB文件的创建与载入

2.1 RDB文件创建

RDB触发机制分为手动触发和自动触发。 

手动触发有两个命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE

SAVE命令会在生成RDB文件过程中阻塞服务器进程,直到RDB文件创建完成。

BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,父进程(服务器进程)继续处理命令请求。

自动触发的配置:

save 900 1 //服务器在900秒之内,对数据库执行了至少1次修改 
save 300 10 //服务器在300秒之内,对数据库执行了至少10修改 
save 60 1000 //服务器在60秒之内,对数据库执行了至少1000修改 
// 满足以上三个条件中的任意一个,则自动触发 BGSAVE 操作 
// 或者使用命令CONFIG SET 命令配置

2.2 BGSAVE命令执行时服务器状态

1、在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝。服务器禁止SAVEBGSAVE同时执行是为了避免父进程和子进程同时指向两个rdbSave调用,防止产生竞争条件。

2、同样的道理,在BGSAVE执行期间,客户端的BGSAVE命令也会被服务器拒绝。

3、另外,对于AOF持久化命令BGREWRITEAOFBGSAVE也同样是互斥关系,如果BGSAVE正在执行,则BGREWRITEAOF命令会被延迟到BGSAVE执行完毕之后;而BGREWRITEAOF命令执行时,服务器会拒绝BGSAVE命令的执行。

4、而事实上,因为BGREWRITEAOF命令与BGSAVE两个命令的实际工作都是由子进程执行,所以这两个命令在操作方面并没有冲突的地方,不能同时执行只是性能方面的考虑--并发处两个子进程,并且这两个子进程同时对磁盘进行大量读写。

书上还画图大概列个流程图。就直接看源码吧,在rdb.c

void saveCommand(client *c) {
	   // BGSAVE 已经在执行中,不能再执行 SAVE
    // 否则将产生竞争条件
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    // 执行 
    if (rdbSave(server.rdb_filename) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

/* BGSAVE [SCHEDULE] */
void bgsaveCommand(client *c) {
    int schedule = 0;  //SCHEDULE控制BGSAVE的执行,避免和AOF重写进程冲突

    /* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite
     * is in progress. Instead of returning an error a BGSAVE gets scheduled. */
    if (c->argc > 1) {
    	 // 设置schedule标志
        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
            schedule = 1;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }
     // 不能重复执行 BGSAVE
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (server.aof_child_pid != -1) {
    	  // 不能在 BGREWRITEAOF 正在运行时执行.提上日程
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
                "An AOF log rewriting in progress: can't BGSAVE right now. "
                "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenver "
                "possible.");
        }
        // 执行BGSAVE
    } else if (rdbSaveBackground(server.rdb_filename) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

后面重点看下rdbSaveBackground的实现。 

2.3 文件载入

    而对于RDB文件的载入而言,其载入工作是在服务器启动时自动执行的,只要RDB服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件。

    另外,对于Redis的另一种持久化方式AOF而言,如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。(因为AOF的更新频率比RDB的高)

三 自动间隔性保存

3.1 间隔性配置

上面2.1节说了自动间隔性配置。服务器会根据save选项所设置的保存条件,设置服务器状态redisServer结构的saveparams属性,saveparams属性是一个数组,每个元素都是一个saveparam结构。

3.2 dirty计数器和lastsave属性

除了saveparams数组之外,Redis服务器状态还维持一个dirty计数器以及一个lastsave属性,这两个属性为判断是否到达save条件提供了便利。

  • 1、 dirty计数器记录距离上一次成功执行SAVEBGSAVE命令之后,服务器对数据库状态进行了多少次修改(包括写入,删除,更新等操作)。
  • 2、 lastsave属性是一个UNIX时间戳,记录上一次成功执行SAVEBGSAVE的时间

3.3 检查保存条件是否满足

Redis服务器周期性执行操作函数server.c/serverCron,默认每隔100毫秒就会执行一次,该函数对于正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,执行BGSAVE命令

程序会遍历并检查saveparams中的保存状态,只要有任意一个条件被满足,就会执行BGSAVE操作。serverCorn函数,通过dirty与lastsave来判断saveparams中保存条件是否被满足。

四 RDB 文件结构

  这一章书上篇幅最长,其实第8章《redis设计与实现》-第8章object 都有。所以不打算跟书上那样细。大概说下就OK。

如上图所示,RDB文件最开头为REDIS部分,通过这五个字符,程序在载入文件时,快速检查所载入的文件是否是RDB文件。

  • db_version表示RDB文件的版本号。
  • databases部分包含零个或多个数据库,以及各个数据库中键值对数据;
  • EOF常量的长度为1字节,这个标志着RDB文件正文内容结束,当读入程序遇到这个值时,证明所有数据库的键值对载入完毕。
  • check_num保存着一个校验和,用于检查RDB文件是否出错或损坏。

4.1 databases部分

RDB文件中的databases部分可以保存任意多个非空数据库。

每个非空数据库在RDB文件中都保存为SELECTDB ,db_number, key_value_pairs三部分:

当程序读取到SELECTDB值时,它知道接下来要读入的是一个数据库号码;
db_number表示的是一个数据库号码,当程序读入db_number部分之后,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切换。
key_value_pairs部分保存数据库中所有键值对,以及过期时间等内容。

4.2 key_value_pairs部分

一个不带过期时间的键值对在RDB文件中由TYPE,key,value三部分组成:

TYPE基类了value的类型,其代表一种对象类型或者底层编码,当服务器读入文件中键值对数据时,程序会根据TYPE的值来决定如何读入和解释value数据。value为不同编码格式时键值对表示实例:这里源码用的就是save跟load的过程。

/* Save the object type of object "o". */
int rdbSaveObjectType(rio *rdb, robj *o) {
    switch (o->type) {
    case OBJ_STRING:
        return rdbSaveType(rdb,RDB_TYPE_STRING);
    case OBJ_LIST:
        if (o->encoding == OBJ_ENCODING_QUICKLIST)
            return rdbSaveType(rdb,RDB_TYPE_LIST_QUICKLIST);
        else
            serverPanic("Unknown list encoding");
    case OBJ_SET:
        if (o->encoding == OBJ_ENCODING_INTSET)
            return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_SET);
        else
            serverPanic("Unknown set encoding");
    case OBJ_ZSET:
        if (o->encoding == OBJ_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_ZSET_ZIPLIST);
        else if (o->encoding == OBJ_ENCODING_SKIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_ZSET);
        else
            serverPanic("Unknown sorted set encoding");
    case OBJ_HASH:
        if (o->encoding == OBJ_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_HASH_ZIPLIST);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_HASH);
        else
            serverPanic("Unknown hash encoding");
    default:
        serverPanic("Unknown object type");
    }
    return -1; /* avoid warning */
}

具体的保存,参见本文后面的源码rdbSaveKeyValuePair()部分。 

总结:

DB的优点:

RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全景复制等场景。
Redis 加载RDB恢复数据远远快于AOF的方式。
RDB的缺点:

RDB没有办法做到实时持久化或秒级持久化。因为BGSAVE每次运行的又要进行fork()的调用创建子进程,这属于重量级操作,频繁执行成本过高,因为虽然Linux支持读时共享,写时拷贝(copy-on-write)的技术,但是仍然会有大量的父进程的空间内存页表,信号控制表,寄存器资源等等的复制。
RDB文件使用特定的二进制格式保存,Redis版本演进的过程中,有多个RDB版本,这导致版本兼容的问题。

五 源码

RDB持久化之前需要设置一些标识,用来标识服务器当前的状态,定义在server.h/struct redisServer 结构体中。部分如下:

struct redisServer {
    // 数据库数组,长度为16
    redisDb *db;
    // 从节点列表和监视器列表
    list *slaves, *qiank;    /* List of slaves and MONITORs */

    /* RDB / AOF loading information ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××*/
    // 正在载入状态
    int loading;                /* We are loading data from disk if true */

    // 设置载入的总字节
    off_t loading_total_bytes;

    // 已载入的字节数
    off_t loading_loaded_bytes;

    // 载入的开始时间
    time_t loading_start_time;

    // 在load时,用来设置读或写的最大字节数max_processing_chunk
    off_t loading_process_events_interval_bytes;

    // 服务器内存使用的
    size_t stat_peak_memory;        /* Max used memory record */

    // 计算fork()的时间
    long long stat_fork_time;       /* Time needed to perform latest fork() */

    // 计算fork的速率,GB/每秒
    double stat_fork_rate;          /* Fork rate in GB/sec. */

    /* RDB persistence ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××*/
    // 脏键,记录数据库被修改的次数
    long long dirty;                /* Changes to DB from the last save */

    // 在BGSAVE之前要备份脏键dirty的值,如果BGSAVE失败会还原
    long long dirty_before_bgsave;  /* Used to restore dirty on failed BGSAVE */

    // 执行BGSAVE的子进程的pid
    pid_t rdb_child_pid;            /* PID of RDB saving child */

    // 保存save参数的数组
    struct saveparam *saveparams;   /* Save points array for RDB */

    // 数组长度
    int saveparamslen;              /* Number of saving points */

    // RDB文件的名字,默认为dump.rdb
    char *rdb_filename;             /* Name of RDB file */

    // 是否采用LZF压缩算法压缩RDB文件,默认yes
    int rdb_compression;            /* Use compression in RDB? */

    // RDB文件是否使用校验和,默认yes
    int rdb_checksum;               /* Use RDB checksum? */

    // 上一次执行SAVE成功的时间
    time_t lastsave;                /* Unix time of last successful save */

    // 最近一个尝试执行BGSAVE的时间
    time_t lastbgsave_try;          /* Unix time of last attempted bgsave */

    // 最近执行BGSAVE的时间
    time_t rdb_save_time_last;      /* Time used by last RDB save run. */

    // BGSAVE开始的时间
    time_t rdb_save_time_start;     /* Current RDB save start time. */

    // 当rdb_bgsave_scheduled为真时,才能开始BGSAVE
    int rdb_bgsave_scheduled;       /* BGSAVE when possible if true. */

    // rdb执行的类型,是写入磁盘,还是写入从节点的socket
    int rdb_child_type;             /* Type of save by active child. */

    // BGSAVE执行完的状态
    int lastbgsave_status;          /* C_OK or C_ERR */

    // 如果不能执行BGSAVE则不能写
    int stop_writes_on_bgsave_err;  /* Don't allow writes if can't BGSAVE */

    // 无磁盘同步,管道的写端
    int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
    // 无磁盘同步,管道的读端
    int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */

    /* time cache ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××*/
    // 保存秒单位的Unix时间戳的缓存
    time_t unixtime;        /* Unix time sampled every cron cycle. */

    // 保存毫秒单位的Unix时间戳的缓存
    long long mstime;       /* Like 'unixtime' but with milliseconds resolution. */

    /* Latency monitor ××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××*/
    // 延迟的阀值
    long long latency_monitor_threshold;
    // 延迟与造成延迟的事件关联的字典
    dict *latency_events;
};

再看上面执行bgsave调用的rdbSaveBackground:

int rdbSaveBackground(char *filename) {
    pid_t childpid;
    long long start;
    // 如果当前有正在进行AOF和RDB操作,否则返回C_ERR
    if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
    //备份当前数据库的脏键值
    server.dirty_before_bgsave = server.dirty;
    // 最近一次尝试执行 BGSAVE 的时间
    server.lastbgsave_try = time(NULL);

    // fork() 开始前的时间,记录 fork() 返回耗时用
    start = ustime();
    //创建子进程
    if ((childpid = fork()) == 0) {
        int retval;

        /* Child */
        //关闭监听的套接字(关闭网络)
        closeListeningSockets(0);
        //设置进程名字,方便识别
        redisSetProcTitle("redis-rdb-bgsave");
        //执行保存操作,把数据库写入到文件
        retval = rdbSave(filename);
        // 打印 copy-on-write 时使用的内存数
        if (retval == C_OK) {
        	//如果做RDB的同时父进程正在写入的数据,那么子进程就会拷贝一个份父进程的内存,而不是和父进程共享一份内存
            size_t private_dirty = zmalloc_get_private_dirty();
             // 将子进程分配的内容写日志
            if (private_dirty) {
                serverLog(LL_NOTICE,
                    "RDB: %zu MB of memory used by copy-on-write",
                    private_dirty/(1024*1024));
            }
        }
         // 子进程退出,发送信号给父进程,发送0表示BGSAVE成功,1表示失败
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent 父进程*/
        // 计算出fork的执行时间
        server.stat_fork_time = ustime()-start;
        // 计算fork的速率,GB/每秒
        server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
        //如果fork执行时长,超过设置的阀值,则要将其加入到一个字典中,与传入"fork"关联,以便进行延迟诊断
        latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
        // 如果fork出错,报告错误
        if (childpid == -1) {
            server.lastbgsave_status = C_ERR;
            serverLog(LL_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return C_ERR;
        }
        // 打印 BGSAVE的日志
        serverLog(LL_NOTICE,"Background saving started by pid %d",childpid);
        server.rdb_save_time_start = time(NULL); //设置BGSAVE开始的时间
        server.rdb_child_pid = childpid;  //设置负责执行BGSAVE操作的子进程id
        server.rdb_child_type = RDB_CHILD_TYPE_DISK; //设置BGSAVE的类型,往磁盘中写入
        updateDictResizePolicy();// 关闭自动 rehash,因为里面有copy操作
        return C_OK;
    }
    return C_OK; /* unreached */
}

    rdbsave的主要流程:就可以看见RDB文件的初始操作,刚开始生成一个临时的RDB文件,只有在执行成功后,才会进行rename操作,然后以写权限打开文件,然后调用了rdbSaveRio()函数将数据库的内容写到临时的RDB文件,之后进行刷新缓冲区和同步操作,就关闭文件进行rename操作和更新服务器状态。

/* Save the DB on disk. Return C_ERR on error, C_OK on success.
 * 将数据库保存到磁盘上。
 * 保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。
 */
int rdbSave(char *filename) {
    char tmpfile[256];
    char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
    FILE *fp;
    rio rdb;
    int error = 0;
      // 创建临时文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
     // 以写方式打开该文件
    fp = fopen(tmpfile,"w");
    // 打开失败,获取文件目录,写入日志
    if (!fp) {
        char *cwdp = getcwd(cwd,MAXPATHLEN);
         // 写日志信息到logfile
        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;
    }
    // 初始化一个rio对象,该对象是一个文件对象IO(关于RIO这块,有待整理)
    rioInitWithFile(&rdb,fp);
    // 将数据库的内容写到rio中
    if (rdbSaveRio(&rdb,&error) == C_ERR) {
        errno = error;
        goto werr;
    }

    /* Make sure data will not remain on the OS's output buffers */
    // 冲洗缓存,确保数据已写入磁盘
    if (fflush(fp) == EOF) goto werr;
    // 将fp指向的文件同步到磁盘中
    if (fsync(fileno(fp)) == -1) goto werr;
    // 关闭文件
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
     //使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。
    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);
        return C_ERR;
    }
    // 写入完成,打印日志
    serverLog(LL_NOTICE,"DB saved on disk");
    server.dirty = 0;// 清零数据库脏状态
    server.lastsave = time(NULL); // 更新上一次SAVE操作的时间
    server.lastbgsave_status = C_OK;// 更新SAVE操作的状态
    return C_OK;

werr:// rdbSaveRio()函数的写错误处理,
    serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));//写日志
    fclose(fp);//关闭文件
    unlink(tmpfile);//删除临时文件
    return C_ERR;//发送C_ERR
}

rdbSaveRio( )我们已经清楚的看到往RDB文件中写了什么内容。这里可以结合上面的RDB文件结构去对应。

/* Produces a dump of the database in RDB format sending it to the specified
 * Redis I/O channel. On success C_OK is returned, otherwise C_ERR
 * is returned and part of the output, or all the output, can be
 * missing because of I/O errors.
 *将一个RDB格式文件内容写入到rio中,成功返回C_OK,否则C_ERR和一部分或所有的出错信息
 * When the function returns C_ERR and if 'error' is not NULL, the
 * integer pointed by 'error' is set to the value of errno just after the I/O
 * error. */
int rdbSaveRio(rio *rdb, int *error) {
    dictIterator *di = NULL;
    dictEntry *de;
    char magic[10];
    int j;
    long long now = mstime();
    uint64_t cksum;

		// 开启了校验和选项
    if (server.rdb_checksum)
    	   // 设置校验和的函数
        rdb->update_cksum = rioGenericUpdateChecksum;
         // 将Redis版本信息保存到magic中
    snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
    // 将magic写到rio中
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
    // 将rdb文件的默认信息写到rio中
    if (rdbSaveInfoAuxFields(rdb) == -1) goto werr;
  // 遍历所有服务器内的数据库
    for (j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;  //当前的数据库指针
        dict *d = db->dict;  //当数据库的键值对字典
        if (dictSize(d) == 0) continue; // 跳过为空的数据库
        di = dictGetSafeIterator(d); // 创建键空间迭代器
        if (!di) return C_ERR;

        /* Write the SELECT DB opcode */
         // 写入数据库的选择标识码 RDB_OPCODE_SELECTDB为254
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
        	// 写入数据库的id,占了一个字节的长度
        if (rdbSaveLen(rdb,j) == -1) goto werr;

        /* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
         * is currently the largest type we are able to represent in RDB sizes.
         * However this does not limit the actual size of the DB to load since
         * these sizes are just hints to resize the hash tables. */
        // 写入调整数据库的操作码,我们将大小限制在UINT32_MAX以内,这并不代表数据库的实际大小,只是提示去重新调整哈希表的大小
        uint32_t db_size, expires_size;
        // 如果字典的大小大于UINT32_MAX,则设置db_size为最大的UINT32_MAX
        db_size = (dictSize(db->dict) <= UINT32_MAX) ?
                                dictSize(db->dict) :
                                UINT32_MAX;
        // 设置有过期时间键的大小超过UINT32_MAX,则设置expires_size为最大的UINT32_MAX
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
                                dictSize(db->expires) :
                                UINT32_MAX;
        // 写入调整哈希表大小的操作码,RDB_OPCODE_RESIZEDB = 251
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
         // 写入提示调整哈希表大小的两个值,如果 	
        if (rdbSaveLen(rdb,db_size) == -1) goto werr;
        if (rdbSaveLen(rdb,expires_size) == -1) goto werr;

        /* Iterate this DB writing every entry */
        //遍历数据库,并写入每个键值对的数据
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de); //当前键
            robj key, *o = dictGetVal(de);//当前键的值
            long long expire;
						 // 根据 keystr ,在栈中创建一个 key 对象
            initStaticStringObject(key,keystr);
            // 当前键的过期时间
            expire = getExpire(db,&key);
            // 保存键值对数据,写入到RIO
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;
        }
        dictReleaseIterator(di);//释放迭代器
    }
    di = NULL; /* So that we don't release it again on error. */

    /* EOF opcode 写入 EOF 代码 */
    if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;

    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
     * loading code skips the check in this case. */
     // CRC64 校验和。 如果校验和功能已关闭,那么 rdb.cksum 将为 0 ,
    //  在这种情况下, RDB 载入时会跳过校验和检查。
    cksum = rdb->cksum;
    memrev64ifbe(&cksum);
    if (rioWrite(rdb,&cksum,8) == 0) goto werr;
    return C_OK;

werr://处理写入错误
    if (error) *error = errno;//保存错误码
    if (di) dictReleaseIterator(di);//如果没有释放迭代器,则释放
    return C_ERR;
}

里面分别调用rdbSaveInfoAuxFields()函数写入一些默认的辅助信息、rdbSaveKeyValuePair写入键值信息,具体如下源码:


/* Save a few default AUX fields with information about the RDB generated. */
// 将一个rdb文件的默认信息写入到rio中
int rdbSaveInfoAuxFields(rio *rdb) {
	 // 判断主机的总线宽度,是64位还是32位
    int redis_bits = (sizeof(void*) == 8) ? 64 : 32;

    /* Add a few fields about the state when the RDB was created. */
      // 添加rdb文件的状态信息:Redis版本,redis位数,当前时间和Redis当前使用的内存数
    if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
    if (rdbSaveAuxFieldStrInt(rdb,"redis-bits",redis_bits) == -1) return -1;
    if (rdbSaveAuxFieldStrInt(rdb,"ctime",time(NULL)) == -1) return -1;
    if (rdbSaveAuxFieldStrInt(rdb,"used-mem",zmalloc_used_memory()) == -1) return -1;
    return 1;
}
/* Save a key-value pair, with expire time, type, key, value.
 * 将键值对的键、值、过期时间和类型写入到 RDB 中。
 * On error -1 is returned.
 * On success if the key was actually saved 1 is returned, otherwise 0
 * is returned (the key was already expired). */
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                        long long expiretime, long long now)
{
    /* Save the expire time */
    //保存键的过期时间
    if (expiretime != -1) {
        /* If this key is already expired skip it */
        //不写入已经过期的键
        if (expiretime < now) return 0;
        if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
    }

    /* Save type, key, value */
    //保存类型,键,值
    if (rdbSaveObjectType(rdb,val) == -1) return -1;
    if (rdbSaveStringObject(rdb,key) == -1) return -1;
    if (rdbSaveObject(rdb,val) == -1) return -1;
    return 1;
}

这里key都是string类型的,value就不同了,看下源码rdbSaveObject:

/* Save a Redis object. Returns -1 on error, number of bytes written on success. */
ssize_t rdbSaveObject(rio *rdb, robj *o) {
    ssize_t n = 0, nwritten = 0;

    if (o->type == OBJ_STRING) {
        /* Save a string value */
        // 存放字符串对象值
        if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
        nwritten += n;
    } else if (o->type == OBJ_LIST) {
        /* Save a list value */
        //保存链表对象值
        if (o->encoding == OBJ_ENCODING_QUICKLIST) {
            quicklist *ql = o->ptr;
            quicklistNode *node = ql->head;
            // 存放长度
            if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;
            nwritten += n;
        
            do {// 判断节点是否能压缩
                if (quicklistNodeIsCompressed(node)) {
                    void *data;
                    // 如能,采用Lzf压缩后写入
                    size_t compress_len = quicklistGetLzf(node, &data);
                    //会保存原始长度,压缩后的长度,压缩后的字符串
                    if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;
                    nwritten += n;
                } else {// 如果不能则直接写入
                    if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
                    nwritten += n;
                }
            } while ((node = node->next));
        } else {//编码错误
            serverPanic("Unknown list encoding");
        }
    } else if (o->type == OBJ_SET) {
        /* Save a set value */
        // 存放集合对象
        if (o->encoding == OBJ_ENCODING_HT) {
        	  // 字典编码的时候,存放长度和键值
            dict *set = o->ptr;
            //创建字典迭代器
            dictIterator *di = dictGetIterator(set);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
            nwritten += n;
						// 遍历每一个键,并存放在RDB中
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);//释放迭代器
        } else if (o->encoding == OBJ_ENCODING_INTSET) {
        	// 直接将INTSET转换成字符串对象存放
            size_t l = intsetBlobLen((intset*)o->ptr);

            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else {
            serverPanic("Unknown set encoding");
        }
    } else if (o->type == OBJ_ZSET) {
        /* Save a sorted set value */
        // 保存有序集合对象值
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        	// 采用ziplist编码的情况,直接按照字符串形式存储
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);

            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
        	// 采用skiplist的情况,遍历所有节点,以次存放
            zset *zs = o->ptr;
            dictIterator *di = dictGetIterator(zs->dict);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
            nwritten += n;
						//skiplist 编码内部有一个dict结构,存放所有的对象和分值
            while((de = dictNext(di)) != NULL) {
                robj *eleobj = dictGetKey(de);
                double *score = dictGetVal(de);

                if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                nwritten += n;
                if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else {
            serverPanic("Unknown sorted set encoding");
        }
    } else if (o->type == OBJ_HASH) {
        /* Save a hash value */
        // 存放hash对象值
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        	// 采用ziplist编码的情况,直接按照字符串形式存储
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);

            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;

        } else if (o->encoding == OBJ_ENCODING_HT) {
        	//hash 迭代,依次获取每个key,value
            dictIterator *di = dictGetIterator(o->ptr);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
            nwritten += n;
          //hash中的每个键值对的key值和value值都以字符串对象的形式相邻存储。
            while((de = dictNext(di)) != NULL) {
                robj *key = dictGetKey(de);
                robj *val = dictGetVal(de);

                if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
                nwritten += n;
                if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);

        } else {
            serverPanic("Unknown hash encoding");
        }

    } else {
        serverPanic("Unknown object type");
    }
    return nwritten;
}

至于string的保存:

/* Like rdbSaveStringObjectRaw() but handle encoded objects */
int rdbSaveStringObject(rio *rdb, robj *obj) {
    /* Avoid to decode the object, then encode it again, if the
     * object is already integer encoded. */
      // 尝试对 INT 编码的字符串进行特殊编码
    if (obj->encoding == OBJ_ENCODING_INT) {
        return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
    } else {  // 保存 STRING 编码的字符串
        serverAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
        return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
    }
}
好了,跟书上的内容都对应了。接下来看rdbLoad函数。


/* Function called at startup to load RDB or AOF file in memory. */
void loadDataFromDisk(void) {
    long long start = ustime();
    if (server.aof_state == AOF_ON) {
        if (loadAppendOnlyFile(server.aof_filename) == C_OK)
            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    } else {
        if (rdbLoad(server.rdb_filename) == C_OK) {
            serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
                (float)(ustime()-start)/1000000);
        } else if (errno != ENOENT) {
            serverLog(LL_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
            exit(1);
        }
    }
}

int rdbLoad(char *filename) {
    uint32_t dbid;
    int type, rdbver;
    redisDb *db = server.db+0;
    char buf[1024];
    long long expiretime, now = mstime();//获取当前load操作的时间
    FILE *fp;
    rio rdb;
    // 只读打开文件
    if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
    	
    // 初始化一个文件流对象rio且设置对应文件指针
    rioInitWithFile(&rdb,fp);
    // 设置计算校验和的函数
    rdb.update_cksum = rdbLoadProgressCallback;
      // 设置载入读或写的最大字节数,2M
    rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
     // 读出9个字节到buf,buf中保存着Redis版本"redis0007"
    if (rioRead(&rdb,buf,9) == 0) goto eoferr;
    buf[9] = '\0'; //"redis0007\0"
     // 检查版本号
    if (memcmp(buf,"REDIS",5) != 0) {
        fclose(fp);
        serverLog(LL_WARNING,"Wrong signature trying to load DB from file");
        errno = EINVAL;
        return C_ERR;
    }
     // 转换成整数检查版本大小
    rdbver = atoi(buf+5);
    if (rdbver < 1 || rdbver > RDB_VERSION) {
        fclose(fp);
        serverLog(LL_WARNING,"Can't handle RDB format version %d",rdbver);
        errno = EINVAL;
        return C_ERR;
    }
     // 设置载入时server的状态信息
    startLoading(fp);
    while(1) {// 开始读取RDB文件到数据库中
        robj *key, *val;
        expiretime = -1;

        /* Read type. 
        * 读入类型指示,决定该如何读入之后跟着的数据。
         *
         * 这个指示可以是 rdb.h 中定义的所有以
         * REDIS_RDB_TYPE_* 为前缀的常量的其中一个
         * 或者所有以 REDIS_RDB_OPCODE_* 为前缀的常量的其中一个
        */
        if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;

        /* Handle special types. */
          // 读入过期时间值
        if (type == RDB_OPCODE_EXPIRETIME) {
            /* EXPIRETIME: load an expire associated with the next key
             * to load. Note that after loading an expire we need to
             * load the actual type, and continue. */
               // 以秒计算的过期时间
            if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
            /* We read the time so we need to read the object type again. */
            // 从过期时间后读出一个键值对的类型
            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
            /* the EXPIRETIME opcode specifies time in seconds, so convert
             * into milliseconds. */
             
            expiretime *= 1000;//转换成毫秒
        } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
        	 // 以毫秒计算的过期时间
            /* EXPIRETIME_MS: milliseconds precision expire times introduced
             * with RDB v3. Like EXPIRETIME but no with more precision. */
            if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
            /* We read the time so we need to read the object type again. */
            //在过期时间之后会跟着一个键值对,我们要读入这个键值对的类型
            if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
        } else if (type == RDB_OPCODE_EOF) {
        	 // 如果读到EOF,则直接跳出循环
            /* EOF: End of file, exit the main loop. */
            break;
        } else if (type == RDB_OPCODE_SELECTDB) {
        	 // 读出的是切换数据库操作
            /* SELECTDB: Select the specified database. */
             // 读取出一个长度,保存的是数据库的ID
            if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
                goto eoferr;
             // 检查读出的ID是否合法
            if (dbid >= (unsigned)server.dbnum) {
                serverLog(LL_WARNING,
                    "FATAL: Data file was created with a Redis "
                    "server configured to handle more than %d "
                    "databases. Exiting\n", server.dbnum);
                exit(1);
            }
              // 在程序内容切换数据库
            db = server.db+dbid;
            continue; /* Read type again. */
        } else if (type == RDB_OPCODE_RESIZEDB) {
        	 // 如果读出调整哈希表的操作
            /* RESIZEDB: Hint about the size of the keys in the currently
             * selected data base, in order to avoid useless rehashing. */
            uint32_t db_size, expires_size;
             // 读出一个数据库键值对字典的大小
            if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
                goto eoferr;
            // 读出一个数据库过期字典的大小      
            if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
                goto eoferr;
            // 扩展两个字典
            dictExpand(db->dict,db_size);
            dictExpand(db->expires,expires_size);
            continue; /* Read type again. */
        } else if (type == RDB_OPCODE_AUX) {
        	// 读出的是一个辅助字段
            /* AUX: generic string-string fields. Use to add state to RDB
             * which is backward compatible. Implementations of RDB loading
             * are requierd to skip AUX fields they don't understand.
             *
             * An AUX field is composed of two strings: key and value. */
            robj *auxkey, *auxval;
             // 读出辅助字段的键对象和值对象
            if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
            if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
             // 键对象的第一个字符是%
            if (((char*)auxkey->ptr)[0] == '%') {
                /* All the fields with a name staring with '%' are considered
                 * information fields and are logged at startup with a log
                 * level of NOTICE. */
                serverLog(LL_NOTICE,"RDB '%s': %s",
                    (char*)auxkey->ptr,
                    (char*)auxval->ptr);
            } else {
                /* We ignore fields we don't understand, as by AUX field
                 * contract. */
                serverLog(LL_DEBUG,"Unrecognized RDB AUX field: '%s'",
                    (char*)auxkey->ptr);
            }

            decrRefCount(auxkey);
            decrRefCount(auxval);
            continue; /* Read type again. */
        }

        /* Read key */
        // 读出一个key对象
        if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
        /* Read value */
         // 读出一个val对象
        if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
        /* Check if the key already expired. This function is used when loading
         * an RDB file from disk, either at startup, or when an RDB was
         * received from the master. In the latter case, the master is
         * responsible for key expiry. If we would expire keys here, the
         * snapshot taken by the master may not be reflected on the slave. */
           // 如果当前环境不是从节点,且该键设置了过期时间,已经过期
        if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
        	 // 释放键值对
            decrRefCount(key);
            decrRefCount(val);
            continue;//跳过
        }
        /* Add the new object in the hash table 
       * 将键值对关联到数据库中
        */
        dbAdd(db,key,val);

        /* Set the expire time if needed 
        *设置过期时间
        */
        if (expiretime != -1) setExpire(db,key,expiretime);

        decrRefCount(key); //释放临时对象
    }
    /* Verify the checksum if RDB version is >= 5 */
     // 当RDB版本大于5时,且开启了校验和的功能,那么进行校验和
    if (rdbver >= 5 && server.rdb_checksum) {
        uint64_t cksum, expected = rdb.cksum;
       // 读入文件的校验和 
        if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
        memrev64ifbe(&cksum);
         // 比对校验和
        if (cksum == 0) {
            serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
        } else if (cksum != expected) {
            serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
            rdbExitReportCorruptRDB("RDB CRC error");
        }
    }
     // 关闭 RDB 
    fclose(fp);
    stopLoading();  // 服务器从载入状态中退出
    return C_OK;
// 错误退出
eoferr: /* unexpected end of file is handled here with a fatal exit */
    serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
    rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
    return C_ERR; /* Just to avoid warning */
}
/* Use rdbLoadType() to load a TYPE in RDB format, but returns -1 if the
 * type is not specifically a valid Object Type. */
int rdbLoadObjectType(rio *rdb) {
    int type;
    if ((type = rdbLoadType(rdb)) == -1) return -1;
    if (!rdbIsObjectType(type)) return -1;
    return type;
}

 load 跟save是想反的过程,就不在朱行注释了

/* Load a Redis object of the specified type from the specified file.
 * On success a newly allocated object is returned, otherwise NULL. */
robj *rdbLoadObject(int rdbtype, rio *rdb) {
    robj *o = NULL, *ele, *dec;
    size_t len;
    unsigned int i;

    if (rdbtype == RDB_TYPE_STRING) {
        /* Read string value */
        if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
        o = tryObjectEncoding(o);
    } else if (rdbtype == RDB_TYPE_LIST) {
        /* Read list value */
        if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;

        o = createQuicklistObject();
        quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
                            server.list_compress_depth);

        /* Load every single element of the list */
        while(len--) {
            if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
            dec = getDecodedObject(ele);
            size_t len = sdslen(dec->ptr);
            quicklistPushTail(o->ptr, dec->ptr, len);
            decrRefCount(dec);
            decrRefCount(ele);
        }
    } else if (rdbtype == RDB_TYPE_SET) {
        /* Read list/set value */
        if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;

        /* Use a regular set when there are too many entries. */
        if (len > server.set_max_intset_entries) {
            o = createSetObject();
            /* It's faster to expand the dict to the right size asap in order
             * to avoid rehashing */
            if (len > DICT_HT_INITIAL_SIZE)
                dictExpand(o->ptr,len);
        } else {
            o = createIntsetObject();
        }

        /* Load every single element of the list/set */
        for (i = 0; i < len; i++) {
            long long llval;
            if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
            ele = tryObjectEncoding(ele);

            if (o->encoding == OBJ_ENCODING_INTSET) {
                /* Fetch integer value from element */
                if (isObjectRepresentableAsLongLong(ele,&llval) == C_OK) {
                    o->ptr = intsetAdd(o->ptr,llval,NULL);
                } else {
                    setTypeConvert(o,OBJ_ENCODING_HT);
                    dictExpand(o->ptr,len);
                }
            }

            /* This will also be called when the set was just converted
             * to a regular hash table encoded set */
            if (o->encoding == OBJ_ENCODING_HT) {
                dictAdd((dict*)o->ptr,ele,NULL);
            } else {
                decrRefCount(ele);
            }
        }
    } else if (rdbtype == RDB_TYPE_ZSET) {
        /* Read list/set value */
        size_t zsetlen;
        size_t maxelelen = 0;
        zset *zs;

        if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
        o = createZsetObject();
        zs = o->ptr;

        /* Load every single element of the list/set */
        while(zsetlen--) {
            robj *ele;
            double score;
            zskiplistNode *znode;

            if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
            ele = tryObjectEncoding(ele);
            if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;

            /* Don't care about integer-encoded strings. */
            if (sdsEncodedObject(ele) && sdslen(ele->ptr) > maxelelen)
                maxelelen = sdslen(ele->ptr);

            znode = zslInsert(zs->zsl,score,ele);
            dictAdd(zs->dict,ele,&znode->score);
            incrRefCount(ele); /* added to skiplist */
        }

        /* Convert *after* loading, since sorted sets are not stored ordered. */
        if (zsetLength(o) <= server.zset_max_ziplist_entries &&
            maxelelen <= server.zset_max_ziplist_value)
                zsetConvert(o,OBJ_ENCODING_ZIPLIST);
    } else if (rdbtype == RDB_TYPE_HASH) {
        size_t len;
        int ret;

        len = rdbLoadLen(rdb, NULL);
        if (len == RDB_LENERR) return NULL;

        o = createHashObject();

        /* Too many entries? Use a hash table. */
        if (len > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);

        /* Load every field and value into the ziplist */
        while (o->encoding == OBJ_ENCODING_ZIPLIST && len > 0) {
            robj *field, *value;

            len--;
            /* Load raw strings */
            field = rdbLoadStringObject(rdb);
            if (field == NULL) return NULL;
            serverAssert(sdsEncodedObject(field));
            value = rdbLoadStringObject(rdb);
            if (value == NULL) return NULL;
            serverAssert(sdsEncodedObject(value));

            /* Add pair to ziplist */
            o->ptr = ziplistPush(o->ptr, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
            o->ptr = ziplistPush(o->ptr, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
            /* Convert to hash table if size threshold is exceeded */
            if (sdslen(field->ptr) > server.hash_max_ziplist_value ||
                sdslen(value->ptr) > server.hash_max_ziplist_value)
            {
                decrRefCount(field);
                decrRefCount(value);
                hashTypeConvert(o, OBJ_ENCODING_HT);
                break;
            }
            decrRefCount(field);
            decrRefCount(value);
        }

        /* Load remaining fields and values into the hash table */
        while (o->encoding == OBJ_ENCODING_HT && len > 0) {
            robj *field, *value;

            len--;
            /* Load encoded strings */
            field = rdbLoadEncodedStringObject(rdb);
            if (field == NULL) return NULL;
            value = rdbLoadEncodedStringObject(rdb);
            if (value == NULL) return NULL;

            field = tryObjectEncoding(field);
            value = tryObjectEncoding(value);

            /* Add pair to hash table */
            ret = dictAdd((dict*)o->ptr, field, value);
            if (ret == DICT_ERR) {
                rdbExitReportCorruptRDB("Duplicate keys detected");
            }
        }

        /* All pairs should be read by now */
        serverAssert(len == 0);
    } else if (rdbtype == RDB_TYPE_LIST_QUICKLIST) {
        if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
        o = createQuicklistObject();
        quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
                            server.list_compress_depth);

        while (len--) {
            unsigned char *zl = rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN);
            if (zl == NULL) return NULL;
            quicklistAppendZiplist(o->ptr, zl);
        }
    } else if (rdbtype == RDB_TYPE_HASH_ZIPMAP  ||
               rdbtype == RDB_TYPE_LIST_ZIPLIST ||
               rdbtype == RDB_TYPE_SET_INTSET   ||
               rdbtype == RDB_TYPE_ZSET_ZIPLIST ||
               rdbtype == RDB_TYPE_HASH_ZIPLIST)
    {
        unsigned char *encoded = rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN);
        if (encoded == NULL) return NULL;
        o = createObject(OBJ_STRING,encoded); /* Obj type fixed below. */

        /* Fix the object encoding, and make sure to convert the encoded
         * data type into the base type if accordingly to the current
         * configuration there are too many elements in the encoded data
         * type. Note that we only check the length and not max element
         * size as this is an O(N) scan. Eventually everything will get
         * converted. */
        switch(rdbtype) {
            case RDB_TYPE_HASH_ZIPMAP:
                /* Convert to ziplist encoded hash. This must be deprecated
                 * when loading dumps created by Redis 2.4 gets deprecated. */
                {
                    unsigned char *zl = ziplistNew();
                    unsigned char *zi = zipmapRewind(o->ptr);
                    unsigned char *fstr, *vstr;
                    unsigned int flen, vlen;
                    unsigned int maxlen = 0;

                    while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
                        if (flen > maxlen) maxlen = flen;
                        if (vlen > maxlen) maxlen = vlen;
                        zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
                        zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
                    }

                    zfree(o->ptr);
                    o->ptr = zl;
                    o->type = OBJ_HASH;
                    o->encoding = OBJ_ENCODING_ZIPLIST;

                    if (hashTypeLength(o) > server.hash_max_ziplist_entries ||
                        maxlen > server.hash_max_ziplist_value)
                    {
                        hashTypeConvert(o, OBJ_ENCODING_HT);
                    }
                }
                break;
            case RDB_TYPE_LIST_ZIPLIST:
                o->type = OBJ_LIST;
                o->encoding = OBJ_ENCODING_ZIPLIST;
                listTypeConvert(o,OBJ_ENCODING_QUICKLIST);
                break;
            case RDB_TYPE_SET_INTSET:
                o->type = OBJ_SET;
                o->encoding = OBJ_ENCODING_INTSET;
                if (intsetLen(o->ptr) > server.set_max_intset_entries)
                    setTypeConvert(o,OBJ_ENCODING_HT);
                break;
            case RDB_TYPE_ZSET_ZIPLIST:
                o->type = OBJ_ZSET;
                o->encoding = OBJ_ENCODING_ZIPLIST;
                if (zsetLength(o) > server.zset_max_ziplist_entries)
                    zsetConvert(o,OBJ_ENCODING_SKIPLIST);
                break;
            case RDB_TYPE_HASH_ZIPLIST:
                o->type = OBJ_HASH;
                o->encoding = OBJ_ENCODING_ZIPLIST;
                if (hashTypeLength(o) > server.hash_max_ziplist_entries)
                    hashTypeConvert(o, OBJ_ENCODING_HT);
                break;
            default:
                rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
                break;
        }
    } else {
        rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
    }
    return o;
}

 

参考:

https://segmentfault.com/a/1190000008639459

https://blog.csdn.net/men_wen/article/details/71248449

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值