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