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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 【Redis源码】轻松看懂rdb文件(四)

前言:

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

一.数据存储格式:

二.查看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

三.源码解析及文件解析

对应源代码:

intrdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
   dictIterator *di = NULL;
   dictEntry *de;
   char magic[10];
   int j;
   longlong 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);
           longlong 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;
}

//辅助字符串方法
intrdbSaveInfoAuxFields(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;
   return1;
}


intrdbSaveAuxField(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
   return1;
}

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 保存键值方法

intrdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                       longlong expiretime, longlong now)

{
   /* 保存过期时间 */
   if (expiretime != -1) {
       /* If this key is already expired skip it */
       if (expiretime < now) return0;
       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;
   return1;
}

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就会写入一个键值长度。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL API Redis
Redis源码(1)基本数据结构(上)
Redis源码(1)基本数据结构
38 2
|
1月前
|
NoSQL 安全 Unix
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅(中)
Redis源码、面试指南(4)单机数据库、持久化、通知与订阅
28 0
|
8天前
|
存储 NoSQL 算法
Redis(四):del/unlink 命令源码解析
Redis(四):del/unlink 命令源码解析
|
1天前
|
存储 NoSQL Redis
ISCONF Redis is configured to save RDB snapshots
ISCONF Redis is configured to save RDB snapshots
|
1月前
|
存储 NoSQL 程序员
Redis(持久化 -- RDB & AOF)
Redis(持久化 -- RDB & AOF)
33 2
|
9天前
|
NoSQL 安全 Redis
redis持久化方式—RDB
redis持久化方式—RDB
25 0
|
1月前
|
NoSQL 安全 算法
Redis源码(1)基本数据结构(中)
Redis源码(1)基本数据结构
47 5
|
1月前
|
存储 NoSQL 算法
Redis源码、面试指南(2)内存编码数据结构(下)
Redis源码、面试指南(2)内存编码数据结构
33 4
|
1月前
|
存储 NoSQL API
Redis源码、面试指南(3)数据对象类型编码(上)
Redis源码、面试指南(3)数据对象类型编码
30 2
|
1月前
|
存储 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(下)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
240 1