【Redis源码学习】轻松看懂rdb文件(四)

前言:

该篇内容为我对redis的学习记录,欢迎指正批评。

 

redis版本:4.0.0

一.数据存储格式:

 

二.查看rdb文件

查看文件16进制编码

#od -A x -t x1c -v dump.rdb

 

 

RDB文件格式如下:

0000000    52  45  44  49  53  30  30  30  38  fa  09  72  65  64  69  73
           R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000010    2d  76  65  72  05  34  2e  30  2e  30  fa  0a  72  65  64  69
           -   v   e   r 005   4   .   0   .   0 372  \n   r   e   d   i
0000020    73  2d  62  69  74  73  c0  40  fa  05  63  74  69  6d  65  c2
           s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e   ¦
0000030    a6  be  1d  5e  fa  08  75  73  65  64  2d  6d  65  6d  c2  80
          ** 276 035   ^ 372  \b   u   s   e   d   -   m   e   m 302 200
0000040    f9  0e  00  fa  0c  61  6f  66  2d  70  72  65  61  6d  62  6c
         371 016  \0 372  \f   a   o   f   -   p   r   e   a   m   b   l
0000050    65  c0  00  fa  07  72  65  70  6c  2d  69  64  28  66  36  31
           e 300  \0 372  \a   r   e   p   l   -   i   d   (   f   6   1
0000060    37  64  35  62  39  62  38  65  32  61  31  63  64  66  39  30
           7   d   5   b   9   b   8   e   2   a   1   c   d   f   9   0
0000070    64  33  35  64  33  32  35  34  32  38  36  62  36  30  38  64
           d   3   5   d   3   2   5   4   2   8   6   b   6   0   8   d
0000080    65  39  66  30  39  fa  0b  72  65  70  6c  2d  6f  66  66  73
           e   9   f   0   9 372  \v   r   e   p   l   -   o   f   f   s
0000090    65  74  c0  00  fe  00  fb  02  00  00  04  6e  61  6d  65  01
           e   t 300  \0 376  \0 373 002  \0  \0 004   n   a   m   e 001
00000a0    61  00  05  74  65  73  74  31  c0  07  ff  0c  73  a2  00  fa
           a  \0 005   t   e   s   t   1 300  \a 377  \f   s 242  \0 372
00000b0    73  ff  b1                                                    
           s 377 261                                                    
00000b3

 

三.源码解析及文件解析

对应源代码:

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;            //写入9个字节
    if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr; //辅助字符串

    for (j = 0; j < server.dbnum; j++) { //遍历db,默认是16
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);
        if (!di) return C_ERR;

        /* 写入 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) ?
                                dictSize(db->dict) :
                                UINT32_MAX;
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
                                dictSize(db->expires) :
                                UINT32_MAX;
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;  //写入数据库 RDB_OPCODE_RESIZEDB对应251
        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);
            expire = getExpire(db,&key);
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;

            /* 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;  //写入8位校验和
    return C_OK;

werr:
    if (error) *error = errno;
    if (di) dictReleaseIterator(di);
    return C_ERR;
}

//辅助字符串方法
int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
    int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
    int aof_preamble = (flags & RDB_SAVE_AOF_PREAMBLE) != 0;
    
     //写入版本
    if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;  
    
    //写入redis(OS arch)
    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; 

    /* Handle saving options that generate aux fields. */
    if (rsi) {
        //写入server.master选择的数据库
        if (rsi->repl_stream_db &&
            rdbSaveAuxFieldStrInt(rdb,"repl-stream-db",rsi->repl_stream_db) 
            == -1)
        {
            return -1;
        }
    }
    
    //写入是否开启混合模式
    if (rdbSaveAuxFieldStrInt(rdb,"aof-preamble",aof_preamble) == -1) return -1;
    
    //写入当前实例replid
    if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid) == -1) return -1;
    
    //当前实例复制的偏移量
    if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset) == -1) return -1;
    return 1;
}


int rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
    if (rdbSaveType(rdb,RDB_OPCODE_AUX) == -1) return -1;   //写入fa
    if (rdbSaveRawString(rdb,key,keylen) == -1) return -1;  //写入key
    if (rdbSaveRawString(rdb,val,vallen) == -1) return -1;  //写入val
    return 1;
}

magic字符串:

52 45 44 49 53 30 30 30 38 这9个字节对应 char magic[10]字符串的9个字节

 

AuxFields辅助字段字符串:

 

字段

备注

redis-ver

版本号

redis-bits

系统位数(OS arch)

ctime

RDB创建文件时间

used-mem

使用内存大小

repl-stream-db

server.master选择的数据库

aof-preamble

是否开启混合模式

repl-id

当前实例replid

repl-offset

当前实例复制的偏移量

 

16进制中应该可以看到fa字样,通用字符串又是怎么识别呢。其实fa是一个分割符,fa后面的一个变量则是具体的key的长度;

如图所示:

 

RDB opcodes

 

常量

值10进制

值16进制

备注

RDB_OPCODE_AUX

250

FA

aux其实就是分割符fa,

RDB_OPCODE_RESIZEDB

251

FB

DB size

RDB_OPCODE_EXPIRETIME_MS

252

FC

过期时间非-1时写入

RDB_OPCODE_EXPIRETIME

253

FD

用于加载和检测rdb时使用

RDB_OPCODE_SELECTDB

254

FE

选择数据库

RDB_OPCODE_EOF

255

FF

结尾

 

从opcode列表我们可以看出从fe开始,fe对应opcode的RDB_OPCODE_SELECTDB。

写入RDB_OPCODE_SELECTDB调用rdbSaveLen,rdbSaveLen函数会计算写入的长度。由于写入比较值小,则是1个字节;

 

rdb.c 保存键值方法

int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                        long long expiretime, long long now)
{
    /* 保存过期时间 */
    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;
    }

    /* 保存 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;
}

 

rdbSaveObjectType保存类型

 

类型

编码

备注

OBJ_STRING

-

RDB_TYPE_STRING

0

字符串

OBJ_LIST

OBJ_ENCODING_QUICKLIST

RDB_TYPE_LIST_QUICKLIST

14

list 双向链表

OBJ_SET

OBJ_ENCODING_INTSET

RDB_TYPE_SET_INTSET

11

整型集合

OBJ_SET

OBJ_ENCODING_HT

RDB_TYPE_SET

2

hash集合

OBJ_ZSET

OBJ_ENCODING_ZIPLIST

RDB_TYPE_ZSET_ZIPLIST

12

zset 压缩表

OBJ_ZSET

OBJ_ENCODING_SKIPLIST

RDB_TYPE_ZSET_2

5

zset 跳跃表

OBJ_HASH

OBJ_ENCODING_ZIPLIST

RDB_TYPE_HASH_ZIPLIST

13

hash 压缩表

OBJ_HASH

OBJ_ENCODING_HT

RDB_TYPE_HASH

4

hash 表

OBJ_MODULE

-

RDB_TYPE_MODULE_2

7

模块

 

根据以上的为写入所有编码。

写入name这个键的时候因为没有写过期时间所以没有过期时间标识和过期时间,然后写入时是用rdbSaveStringObject,

rdbSaveStringObject由于不是整形编码,则调用了rdbSaveRawString函数。rdbSaveRawString就会写入一个键值长度。

 

发布了12 篇原创文章 · 获赞 1 · 访问量 255
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览