程序地带

redis自顶向下源码阅读(十一)——读取 RDB 文件1


redis自顶向下源码阅读(十一)——读取 RDB 文件1
1. 声明变量
int rdbLoad(char *filename) {
uint32_t dbid;
int type, rdbver;
redisDb *db = server.db+0;
char buf[1024];
long long expiretime, now = mstime();
FILE *fp;
// 专门用于 io 的数据结构
rio rdb;
// 判断文件是否正常打开
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
2. 对rio数据结构初始化
int rdbLoad(char *filename) {
rioInitWithFile(&rdb,fp);
// 该函数用于计算 rdb 的校验和
rdb.update_cksum = rdbLoadProgressCallback;
// 单次最大读写的块大小
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
}
void rioInitWithFile(rio *r, FILE *fp) {
*r = rioFileIO;
r->io.file.fp = fp;
r->io.file.buffered = 0;
r->io.file.autosync = 0;
}
// 用于 rio 的初始化,里面包含 rdb 文件 io 的函数
static const rio rioFileIO = {
rioFileRead,
rioFileWrite,
// 得到文件位置指针当前位置相对于文件首的偏移字节数
rioFileTell,
// 用于刷新 fp 的缓冲区
rioFileFlush,
NULL, /* update_checksum */
0, /* current checksum */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
3. 解析REDIS版本号
// 读取文件的前 9 个字节
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
buf[9] = '';
// 判断前 5 个字节是不是 REDIS
if (memcmp(buf,"REDIS",5) != 0) {
fclose(fp);
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
errno = EINVAL;
return REDIS_ERR;
}
// 解析 REDIS 后的版本号
rdbver = atoi(buf+5);
if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
errno = EINVAL;
return REDIS_ERR;
}
4. 设置startingloading的状态
// startLoading(fp);
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */
void startLoading(FILE *fp) {
struct stat sb;
/* Load the DB */
server.loading = 1;
server.loading_start_time = time(NULL);
server.loading_loaded_bytes = 0;
if (fstat(fileno(fp), &sb) == -1) {
server.loading_total_bytes = 0;
} else {
server.loading_total_bytes = sb.st_size;
}
}
5. 载入rdb文件
5.1. 读取EXPIRETIME过期时间
while(1) {
robj *key, *val;
expiretime = -1;
/* Read type. */
// 因为 rdb 文件中的每个数据前都有一个 type 用于判断后面的数据类型
// 所以每次都需要先读取 type
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
// REDIS_RDB_OPCODE_EXPIRETIME: 表示后面的数据是该对象的过期时间
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
/* the EXPIRETIME opcode specifies time in seconds, so convert
* into milliseconds. */
expiretime *= 1000;
// REDIS_RDB_OPCODE_EXPIRETIME_MS: 表示过期时间单位是毫秒
} else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
/* Milliseconds precision expire times introduced with RDB
* version 3. */
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
}
// REDIS_RDB_OPCODE_EOF: End of the RDB file.
if (type == REDIS_RDB_OPCODE_EOF)
break;
5.2. 读取数据库id
/* Handle SELECT DB opcode as a special case */
// REDIS_RDB_OPCODE_SELECTDB: 表示开始读取数据库
if (type == REDIS_RDB_OPCODE_SELECTDB) {
// 读取数据库 id
if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting ", server.dbnum);
exit(1);
}
db = server.db+dbid;
continue;
}

因为dbid有长有短(最长的时候32bit,每次都用32bit存dbid有点浪费),所以先利用了两位bit的空间来设定dbid的长度,这样大多数时间只需要一个字节或者两个字节就够了,以更好地利用存储空间。


uint32_t rdbLoadLen(rio *rdb, int *isencoded) {
unsigned char buf[2];
uint32_t len;
int type;
if (isencoded) *isencoded = 0;
if (rioRead(rdb,buf,1) == 0) return REDIS_RDB_LENERR;
// 取高 2 位
type = (buf[0]&0xC0)>>6;
if (type == REDIS_RDB_ENCVAL) {
/* Read a 6 bit encoding type. */
if (isencoded) *isencoded = 1;
// 取低 6 位
return buf[0]&0x3F;
} else if (type == REDIS_RDB_6BITLEN) {
/* Read a 6 bit len. */
// 取低 6 位
return buf[0]&0x3F;
} else if (type == REDIS_RDB_14BITLEN) {
/* Read a 14 bit len. */
// 取低 14 位
if (rioRead(rdb,buf+1,1) == 0) return REDIS_RDB_LENERR;
return ((buf[0]&0x3F)<<8)|buf[1];
} else {
/* Read a 32 bit len. */
// 取后 4 个字节
if (rioRead(rdb,&len,4) == 0) return REDIS_RDB_LENERR;
return ntohl(len);
}
}
5.3. 读取key-value

读取key

key使用的是无编码的string

/* Read key */
// if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
// robj *rdbLoadStringObject(rio *rdb) {
// return rdbGenericLoadStringObject(rdb,0);
// }
robj *rdbGenericLoadStringObject(rio *rdb, int encode) {
int isencoded;
uint32_t len;
robj *o;
// string 前有一个表示长度的值,存储方式同上 dbid
len = rdbLoadLen(rdb,&isencoded);
if (isencoded) {
switch(len) {
case REDIS_RDB_ENC_INT8:
case REDIS_RDB_ENC_INT16:
case REDIS_RDB_ENC_INT32:
return rdbLoadIntegerObject(rdb,len,encode);
case REDIS_RDB_ENC_LZF:
return rdbLoadLzfStringObject(rdb);
default:
redisPanic("Unknown RDB encoding type");
}
}
if (len == REDIS_RDB_LENERR) return NULL;
// 对于是否编码,有两种创建 string 对象的方式
o = encode ? createStringObject(NULL,len) :
createRawStringObject(NULL,len);
if (len && rioRead(rdb,o->ptr,len) == 0) {
decrRefCount(o);
return NULL;
}
return o;
}

读取value:内容太多了,放下一节吧


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_33168253/article/details/109757236

随机推荐

pandas更新

https://blog.csdn.net/weixin_47728930/article/details/106882995...

伍岳凌 阅读(174)

OFDM基础仿真Matlab实现

原理参考文章:OFDM的基本原理剖析基于matlab的ofdm系统设计与仿真…写程序前一定要理清原理思路,网上可参考资料很多,要综合学习,吸收所...

Potato` 阅读(665)

《汇编语言》上机实验内容//理解

《汇编语言》上机实验内容//理解

【实验目标要求】《汇编语言》是计算机科学与技术专业必修的专业基础课程。汇编语言程序设计实验的目标是学习汇编语言程序设计的基本方法和技能,熟练掌握用汇编语言设计、编写、调试和运行程序的方法...

0_3 阅读(491)

前端后台_中后台产品前端通用解决方案 DevUI Design

DevUIDesign是一款企业中后台产品前端的通用解决方案,设计系统包含了DevUI规则、设计语言和最佳实践的资源组合。DevUIDesign可以让开发人员更加专注于应用逻辑的思考&#...

李超字子躍 阅读(867)