顺风车运营研发团队 肖涛
概要:
1、 阅读rdbSave源码
2、 数据结构 rdbSaveInfo
3、 rdb和aof的区别
4、 redis的RIO和BIO
5、 redis的opcode
6、数据结构redisobj
7、redis的lzf压缩,压缩函数rdbSaveLzfBlob
详情:
1、rdbSave函数。
redis提供了rdb持久化的功能,通过rdb持久化会生成rdb文件的压缩二进制文件,也可以通过该文件还原数据。
主要是save和bgsave两个命令。save是同步执行,会阻塞,bgsave命令是异步,会再启动一个进程,所以不会阻塞。
以save命令为例。 源码如下:
void saveCommand(client *c) {
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
return;
}
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
}
}
rdbSave中主要的函数rdbSaveRio ,rdbSaveRio是以rdb格式生成数据库存储,并将其发送到指定文件。
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; // rdbWriteRaw 写入数据到rdb中
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) { // 遍历db,默认是16个 从0 - 15
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetSafeIterator(d);
if (!di) return C_ERR;
/* Write the 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) ? // 算出dbsize 所有的key-v hash表
dictSize(db->dict) :
UINT32_MAX;
expires_size = (dictSize(db->expires) <= UINT32_MAX) ? // 过期表的 hash表
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr; // 将dbsize和 expire hash表存在
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); //获取key后将 key字符串编码成redisobj对象
expire = getExpire(db,&key); // 获取过期时间
if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr; // 写入k-v和过期时间
/* 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;
return C_OK;
werr:
if (error) *error = errno;
if (di) dictReleaseIterator(di);
return C_ERR;
}
在写入key-v键值对儿时,如果过期时间非-1 会先写入RDB_OPCODE_EXPIRETIME_MS的opcode,在这之后依次写入 type、key、val。这里type是redis支持的类型,key会转化成字符串对象
redisObject 进行存储。写入val是调用rdbSaveObject函数实现,为了节省空间保存val时会根据其类型判断是否选择压缩算法。
以obj_list类型为例, 在存储是会选择是否使用lzf压缩算法。
lzf压缩算法示意图如下: lzf的核心是用一个字节中的高两位来存储val的类型。这里用2个bit位是因为在设计上根据长度分为4中类型 6-bit 、14-bit,32-bit,64-bit
![图片上传中...]
rdb的opcode,定义在头文件中
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_AUX 250
#define RDB_OPCODE_RESIZEDB 251
#define RDB_OPCODE_EXPIRETIME_MS 252
#define RDB_OPCODE_EXPIRETIME 253
#define RDB_OPCODE_SELECTDB 254
#define RDB_OPCODE_EOF 255
/* Module serialized values sub opcodes */
#define RDB_MODULE_OPCODE_EOF 0 /* End of module value. */
#define RDB_MODULE_OPCODE_SINT 1 /* Signed integer. */
#define RDB_MODULE_OPCODE_UINT 2 /* Unsigned integer. */
#define RDB_MODULE_OPCODE_FLOAT 3 /* Float. */
#define RDB_MODULE_OPCODE_DOUBLE 4 /* Double. */
#define RDB_MODULE_OPCODE_STRING 5 /* String. */
在保存rdb文件是,通过od -c dump.rdb 可以查看rdb文件
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。