【Redis学习笔记】2018-06-06 rdb相关信息

作者:顺风车运营研发团队 肖涛
概要:

1、 阅读rdbSave源码

2、 数据结构 rdbSaveInfo

3、 rdb和aof的区别

4、 redis的RIO和BIO

5、 redis的opcode

6、数据结构redisobj

7、redis的lzf压缩,压缩函数rdbSaveLzfBlob
详情:

1、rdbSave函数。

redis提供了rdb持久化的功能,通过rdb持久化会生成rdb文件的压缩二进制文件,也可以通过该文件还原数据。

主要是save和bgsave两个命令。save是同步执行,会阻塞,bgsave命令是异步,会再启动一个进程,所以不会阻塞。

以save命令为例。 源码如下:

void saveCommand(client *c) {
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {   
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

rdbSave中主要的函数rdbSaveRio ,rdbSaveRio是以rdb格式生成数据库存储,并将其发送到指定文件。

int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
    dictIterator *di = NULL;
    dictEntry *de;
    char magic[10];
    int j;
    long long now = mstime();
    uint64_t cksum;
    size_t processed = 0;
 
    if (server.rdb_checksum)
        rdb->update_cksum = rioGenericUpdateChecksum;  
    snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;            // rdbWriteRaw 写入数据到rdb中
    if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
 
    for (j = 0; j < server.dbnum; j++) {        // 遍历db,默认是16个 从0 - 15
        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 */
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
        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_t db_size, expires_size;
        db_size = (dictSize(db->dict) <= UINT32_MAX) ?     // 算出dbsize   所有的key-v hash表
                                dictSize(db->dict) :
                                UINT32_MAX;
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?   //  过期表的 hash表
                                dictSize(db->expires) :
                                UINT32_MAX;
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;   // 将dbsize和 expire hash表存在
        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;
 
            initStaticStringObject(key,keystr);   //获取key后将 key字符串编码成redisobj对象
            expire = getExpire(db,&key);  // 获取过期时间
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;  // 写入k-v和过期时间
 
            /* When this RDB is produced as part of an AOF rewrite, move
             * accumulated diff from parent to child while rewriting in
             * order to have a smaller final write. */
            if (flags & RDB_SAVE_AOF_PREAMBLE &&
                rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
            {
                processed = rdb->processed_bytes;
                aofReadDiffFromParent();
            }
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */
 
    /* EOF opcode */
    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. */
    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;
}

在写入key-v键值对儿时,如果过期时间非-1 会先写入RDB_OPCODE_EXPIRETIME_MS的opcode,在这之后依次写入 type、key、val。这里type是redis支持的类型,key会转化成字符串对象
redisObject 进行存储。写入val是调用rdbSaveObject函数实现,为了节省空间保存val时会根据其类型判断是否选择压缩算法。 
以obj_list类型为例, 在存储是会选择是否使用lzf压缩算法。 
lzf压缩算法示意图如下: lzf的核心是用一个字节中的高两位来存储val的类型。这里用2个bit位是因为在设计上根据长度分为4中类型 6-bit 、14-bit,32-bit,64-bit

![图片上传中...]

rdb的opcode,定义在头文件中

/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_AUX        250
#define RDB_OPCODE_RESIZEDB   251
#define RDB_OPCODE_EXPIRETIME_MS 252
#define RDB_OPCODE_EXPIRETIME 253
#define RDB_OPCODE_SELECTDB   254
#define RDB_OPCODE_EOF        255
 
/* Module serialized values sub opcodes */
#define RDB_MODULE_OPCODE_EOF   0   /* End of module value. */
#define RDB_MODULE_OPCODE_SINT  1   /* Signed integer. */
#define RDB_MODULE_OPCODE_UINT  2   /* Unsigned integer. */
#define RDB_MODULE_OPCODE_FLOAT 3   /* Float. */
#define RDB_MODULE_OPCODE_DOUBLE 4  /* Double. */
#define RDB_MODULE_OPCODE_STRING 5  /* String. */

在保存rdb文件是,通过od -c dump.rdb 可以查看rdb文件

clipboard.png

猜你喜欢

转载自blog.csdn.net/weixin_42737865/article/details/81116218