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

20 篇文章 3 订阅

前言:

该篇内容为我对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就会写入一个键值长度。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis是一个开源的内存数据存储系统,可以用作数据库、缓存和消息队列等。它支持多种数据结构,如字符串、列表、哈希、集合和有序集合等。 在Redis中,RDB文件是一种持久化的方式,用于将内存中的数据保存到硬盘中。当我们使用RDB文件进行启动时,Redis会将RDB文件加载到内存中,从而恢复原来的数据状态。 RDB文件是通过Redis快照功能生成的,可以通过执行SAVE或BGSAVE命令手动创建RDB文件,也可以根据配置文件中设置的自动快照触发条件周期性地创建RDB文件。 当我们通过RDB文件启动Redis时,首先需要将RDB文件放在Redis的工作目录下。然后,在启动Redis时,可以通过命令行的方式指定RDB文件的路径,例如: redis-server /path/to/redis.conf --dir /path/to/rdb/file 这样,Redis就会加载RDB文件,并将其中的数据恢复到内存中。启动完成后,Redis将可以使用之前保存在RDB文件中的数据。 通过RDB文件启动Redis的优点是恢复速度快,因为RDB文件保存了Redis的快照,加载RDB文件只需要将文件中的数据读取到内存中即可。同时,RDB文件的大小相对较小,占用的磁盘空间较少。 需要注意的是,使用RDB文件进行启动时,最好先备份好最新的RDB文件,以免数据丢失。另外,RDB文件只保存了快照时刻的数据,因此如果在最新RDB文件生成之后有新数据写入,这部分数据是无法恢复的。为了避免数据丢失,还可以将AOF日志功能与RDB文件一起使用,将数据的修改操作追加到AOF日志文件中,确保数据的持久性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值