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] = '\0';
// 判断前 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\n", 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
:内容太多了,放下一节吧