一、数据存储格式
二、查看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 73R 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 c2s - 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 6c371 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 31e 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 307 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 64d 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 73e 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 01e 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 faa \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,默认是16redisDb *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对应251if (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;//写入当前实例replidif (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; //写入faif (rdbSaveRawString(rdb,key,keylen) == -1) return -1; //写入keyif (rdbSaveRawString(rdb,val,vallen) == -1) return -1; //写入valreturn 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
从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保存类型
根据以上的为写入所有编码。
写入name这个键的时候因为没有写过期时间所以没有过期时间标识和过期时间,然后写入时是用rdbSaveStringObject,
rdbSaveStringObject由于不是整形编码,则调用了rdbSaveRawString函数。rdbSaveRawString就会写入一个键值长度。
原创 赵禹 爱因诗贤