Redis有检查RDB文件的功能 用来看RDB是否正常
主要实现 在
int redis_check_rdb(char *rdbfilename, FILE *fp)
这个函数 现在详细介绍一下
目录
一.声明了一些变量
uint64_t dbid;
int selected_dbid = -1;//被选中的数据库ID
int type, rdbver;//RDB文件的数据类型 RDB版本号
char buf[1024];//读取
long long expiretime, now = mstime();//过期 和当前
static rio rdb; /* Pointed by global struct riostate. */
struct stat sb;//stat结构体
二.检查文件状态并初始化一些状态
int closefile = (fp == NULL);
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
判断是否需要打开RDB文件(fp为空) 打开后 用fstat看文件状态信息 并将结果存到sb结构体
看文件大小是否为0
startLoadingFile(sb.st_size, rdbfilename, RDBFLAGS_NONE);
//初始化一些相关的状态。
rioInitWithFile(&rdb,fp);
rdbstate.rio = &rdb;
//将 RDB 文件与 &rdb 进行关联,方便读取
rdb.update_cksum = rdbLoadProgressCallback;
//这是一个 RDB 文件加载过程的回调函数,用于更新校验和。
startLoadingFile这个函数 不用多说 就是初始化一些状态 方便统计
void startLoading(size_t size, int rdbflags, int async) {
/* Load the DB */
server.loading = 1;
if (async == 1) server.async_loading = 1;
server.loading_start_time = time(NULL);
server.loading_loaded_bytes = 0;
server.loading_total_bytes = size;
server.loading_rdb_used_mem = 0;
server.rdb_last_load_keys_expired = 0;
server.rdb_last_load_keys_loaded = 0;
blockingOperationStarts();
/* Fire the loading modules start event. */
int subevent;
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
subevent = REDISMODULE_SUBEVENT_LOADING_AOF_START;
else if(rdbflags & RDBFLAGS_REPLICATION)
subevent = REDISMODULE_SUBEVENT_LOADING_REPL_START;
else
subevent = REDISMODULE_SUBEVENT_LOADING_RDB_START;
moduleFireServerEvent(REDISMODULE_EVENT_LOADING,subevent,NULL);
}
rioInitWithFile
void rioInitWithFile(rio *r, FILE *fp) {
*r = rioFileIO;
r->io.file.fp = fp;
r->io.file.buffered = 0;//表示不进行缓冲操作
r->io.file.autosync = 0;//表示不自动同步写入文件
r->io.file.reclaim_cache = 0;//表示不进行缓存回收操作
}
rio结构体是一个抽象的 I/O 操作接口 这就不做详细介绍了
rdbLoadProgressCallback 用于跟踪加载进度并在需要时计算 RDB 校验和 以便与客户不时进行提供服务。这里也细讲了
三.读取开头 看是不是RDB格式
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
//读取 9 个字节到buf 中,如果返回值为 0,则表示读取到文件末尾
buf[9] = '\0';
if (memcmp(buf,"REDIS",5) != 0) {
rdbCheckError("Wrong signature trying to load DB from file");
goto err;
}
//使用 memcmp 判断 buf 中的前 5 个字节是否与字符串 “REDIS” 相等
rdbver = atoi(buf+5);
if (rdbver < 1 || rdbver > RDB_VERSION) {
rdbCheckError("Can't handle RDB format version %d",rdbver);
goto err;
}
expiretime = -1;
这里判断 buf前5个字符是否为"REDIS" 是因为REDIS在写RDB的时候 会把版本信息写到开头 是RDB特有的格式 所以如果这开头的字符串不是"REDIS"则说明不是RDB文件
-------------------------------------------------------------------------------------------------------------------------
来看看这里的rioRead的实现
static inline size_t rioRead(rio *r, void *buf, size_t len) {
if (r->flags & RIO_FLAG_READ_ERROR) return 0;
//r 对象的 flags和RIO_FLAG_READ_ERROR ,来检测是否发生了读取错误。如果发生了读取错误,则直接表示读取失败。
while (len) {//读取完成指定长度的数据 len
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;//合理分配空间
if (r->read(r,buf,bytes_to_read) == 0) {
r->flags |= RIO_FLAG_READ_ERROR;
//如果读取失败(返回0),就将RIO_FLAG_READ_ERROR标志位添加到r->flags
return 0;
}
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);//更新检查和
buf = (char*)buf + bytes_to_read;
len -= bytes_to_read;
r->processed_bytes += bytes_to_read;//加上bytes_to_read表示已处理的字节数
}
return 1;
}
看得出这个读取函数有两个特别之处
、1.在发生错误的情况下立即停止读取,以防止错误的扩大。
2.校验和
再看看rdbCheckError函数
void rdbCheckError(const char *fmt, ...) {
char msg[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
printf("--- RDB ERROR DETECTED ---\n");//表示检测到 RDB 错误。
printf("[offset %llu] %s\n",
(unsigned long long) (rdbstate.rio ?
rdbstate.rio->processed_bytes : 0), msg);
printf("[additional info] While doing: %s\n",
rdb_check_doing_string[rdbstate.doing]);//打印错误发生时正在进行的操作的信息
//打印错误消息和偏移量信息。偏移量是指在错误发生时正在处理的字节偏移量。
if (rdbstate.key)
printf("[additional info] Reading key '%s'\n",
(char*)rdbstate.key->ptr);
//如果存在 rdbstate.key,则打印正在读取的键的信息
if (rdbstate.key_type != -1)
printf("[additional info] Reading type %d (%s)\n",
//不等于 - 1,则打印正在读取的键的类型和对应的字符串表示
rdbstate.key_type,
((unsigned)rdbstate.key_type <
sizeof(rdb_type_string)/sizeof(char*)) ?
rdb_type_string[rdbstate.key_type] : "unknown");
rdbShowGenericInfo(); //函数调用了 rdbShowGenericInfo 函数,打印出通用信息,包括已读取的键的数量、设置了过期时间的键的数量和已过期的键的数量....
}
在RDB错误时调用。提供有关RDB和偏移量的详细信息
这里的vs_start 和end是可变参数模板 我曾写过一篇博客介绍他的go语言的实现方式
四.正式读取
while(1) {
robj *key, *val;
/* Read type. */
rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
/* Handle special types. */
if (type == RDB_OPCODE_EXPIRETIME) {
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
expiretime = rdbLoadTime(&rdb);
expiretime *= 1000;
if (rioGetReadError(&rdb)) goto eoferr;
continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
expiretime = rdbLoadMillisecondTime(&rdb, rdbver);
if (rioGetReadError(&rdb)) goto eoferr;
continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_FREQ) {
/* FREQ: LFU frequency. */
uint8_t byte;
if (rioRead(&rdb,&byte,1) == 0) goto eoferr;
continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_IDLE) {
/* IDLE: LRU idle time. */
/*IDLE:LRU空闲时间*/
if (rdbLoadLen(&rdb,NULL) == RDB_LENERR) goto eoferr;
continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
break;
} else if (type == RDB_OPCODE_SELECTDB) {
/* SELECTDB: Select the specified database. */
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
selected_dbid = dbid;
continue; /* Read type again. */
} else if (type == RDB_OPCODE_RESIZEDB) {
/* RESIZEDB: Hint about the size of the keys in the currently
* selected data base, in order to avoid useless rehashing. */
uint64_t db_size, expires_size;
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
continue; /* Read type again. */
} else if (type == RDB_OPCODE_AUX) {
/* AUX: generic string-string fields. Use to add state to RDB
* which is backward compatible. Implementations of RDB loading
* are required to skip AUX fields they don't understand.
*
* An AUX field is composed of two strings: key and value. */
robj *auxkey, *auxval;
rdbstate.doing = RDB_CHECK_DOING_READ_AUX;
if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) {
decrRefCount(auxkey);
goto eoferr;
}
rdbCheckInfo("AUX FIELD %s = '%s'",
(char*)auxkey->ptr, (char*)auxval->ptr);
decrRefCount(auxkey);
decrRefCount(auxval);
continue; /* Read type again. */
} else if (type == RDB_OPCODE_MODULE_AUX) {
/* AUX: Auxiliary data for modules. */
uint64_t moduleid, when_opcode, when;
rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX;
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
rdbCheckError("bad when_opcode");
goto err;
}
char name[10];
moduleTypeNameByID(name,moduleid);
rdbCheckInfo("MODULE AUX for: %s", name);
robj *o = rdbLoadCheckModuleValue(&rdb,name);
decrRefCount(o);
continue; /* Read type again. */
} else if (type == RDB_OPCODE_FUNCTION_PRE_GA) {
rdbCheckError("Pre-release function format not supported %d",rdbver);
goto err;
} else if (type == RDB_OPCODE_FUNCTION2) {
sds err = NULL;
rdbstate.doing = RDB_CHECK_DOING_READ_FUNCTIONS;
if (rdbFunctionLoad(&rdb, rdbver, NULL, 0, &err) != C_OK) {
rdbCheckError("Failed loading library, %s", err);
sdsfree(err);
goto err;
}
continue;
} else {
if (!rdbIsObjectType(type)) {
rdbCheckError("Invalid object type: %d", type);
goto err;
}
rdbstate.key_type = type;
}
/* Read key */
rdbstate.doing = RDB_CHECK_DOING_READ_KEY;
//rdbstate.doing 的值为 RDB_CHECK_DOING_READ_KEY,表示当前正在读取键。
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
rdbstate.key = key;
rdbstate.keys++;
/* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
if ((val = rdbLoadObject(type,&rdb,key->ptr,selected_dbid,NULL)) == NULL) goto eoferr;
/* Check if the key already expired. */
if (expiretime != -1 && expiretime < now)
rdbstate.already_expired++;
if (expiretime != -1) rdbstate.expires++;
rdbstate.key = NULL;
decrRefCount(key);
decrRefCount(val);
rdbstate.key_type = -1;
expiretime = -1;
}
进入一个while循环 根据读取出来的类型执行相应的操作
robj *key, *val;
//声明了 key 和 val 两个指向 Redis 对象的指针,用于存储读取到的键和值。
/* Read type. */
rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
// RDB_CHECK_DOING_READ_TYPE,表示当前正在读取数据段的类型。
//使用 rdbLoadType 函数从 rdb 中读取数据段的类型 如果返回值为 - 1,表示读取到了文件末尾
int rdbLoadType(rio *rdb) {
unsigned char type;
if (rioRead(rdb,&type,1) == 0) return -1;
return type;
}//这里读取只读取一个字节
1.RDB_OPCODE_EXPIRETIME
当读取出来的type为RDB_OPCODE_EXPIRETIME
if (type == RDB_OPCODE_EXPIRETIME) {
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
/*EXPIRETIME:加载与下一个密钥关联的过期
*以加载。请注意,加载过期后,我们需要
*加载实际类型,然后继续*/
expiretime = rdbLoadTime(&rdb);
expiretime *= 1000;
if (rioGetReadError(&rdb)) goto eoferr;
continue; /* Read next opcode. */
/* 如果类型是 RDB_OPCODE_EXPIRETIME,表示该键值对有过期时间。
接下来读取过期时间并保存到 expiretime 变量中,单位是毫秒。
并检查是否出错*/
}
rdbLoadTime
time_t rdbLoadTime(rio *rdb) {
int32_t t32;
if (rioRead(rdb,&t32,4) == 0) return -1;
return (time_t)t32;
}
并看过去读取时有没有错误
static inline int rioGetReadError(rio *r) {
return (r->flags & RIO_FLAG_READ_ERROR) != 0;
}
2. RDB_OPCODE_EXPIRETIME_MS
else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
/*EXPIRETIME_MS:引入毫秒精度的过期时间
*使用RDB v3。像EXPIRETIME,但没有更精确*/
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
expiretime = rdbLoadMillisecondTime(&rdb, rdbver);
if (rioGetReadError(&rdb)) goto eoferr;
continue; /* Read next opcode. */
}
不过这里的过期时间精确到毫秒级别。使用 rdbLoadMillisecondTime 函数读取过期时间,并保存到 expiretime 变量中,再次检查读取错误。然后继续下一轮循环。
3.RDB_OPCODE_FREQ
else if (type == RDB_OPCODE_FREQ) {
/* FREQ: LFU frequency. */
uint8_t byte;
if (rioRead(&rdb,&byte,1) == 0) goto eoferr;
continue; /* Read next opcode. */
}
当前代码段并未使用该信息,继续下一轮循环。
4.RDB_OPCODE_IDLE
else if (type == RDB_OPCODE_IDLE) {
/* IDLE: LRU idle time. */
/*IDLE:LRU空闲时间*/
if (rdbLoadLen(&rdb,NULL) == RDB_LENERR) goto eoferr;
continue; /* Read next opcode. */
//通过 rdbLoadLen 函数读取数据段的长度
}
---------------------------------------------------------------------------------------------------------------------------------
介绍一下这里的rdbLoadLen
uint64_t rdbLoadLen(rio *rdb, int *isencoded) {
uint64_t len;
//用于存储加载到的长度值。
if (rdbLoadLenByRef(rdb,isencoded,&len) == -1) return RDB_LENERR;
// isencoded 参数记录长度值是否经过编码
return len;
}
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr) {
unsigned char buf[2];
int type;
if (isencoded) *isencoded = 0; //为 0,表示长度值未经过编码。
if (rioRead(rdb,buf,1) == 0) return -1; //通过调用 rioRead 函数从 RDB 对象中读取一个字节,并将其存储到 buf 数组中
type = (buf[0]&0xC0)>>6; /
if (type == RDB_ENCVAL) {
/* Read a 6 bit encoding type. */
if (isencoded) *isencoded = 1;
*lenptr = buf[0]&0x3F;
} else if (type == RDB_6BITLEN) {
/* Read a 6 bit len. */
*lenptr = buf[0]&0x3F;
} else if (type == RDB_14BITLEN) {
/* Read a 14 bit len. */
// 通过读取下一个字节 buf[1],将 buf[0] 中的低 6 位和 buf[1] 组合成 14 位的长度值。
if (rioRead(rdb,buf+1,1) == 0) return -1;
*lenptr = ((buf[0]&0x3F)<<8)|buf[1];
} else if (buf[0] == RDB_32BITLEN) {
/* Read a 32 bit len. */
uint32_t len;
if (rioRead(rdb,&len,4) == 0) return -1;
*lenptr = ntohl(len);
//通过读取接下来的 4 个字节,将这 4 个字节解析为一个 32 位的长度值 进行网络字节序的转换(由 ntohl 函数执行)。
} else if (buf[0] == RDB_64BITLEN) {
/* Read a 64 bit len. */
uint64_t len;
if (rioRead(rdb,&len,8) == 0) return -1;
*lenptr = ntohu64(len);
//uf[0] 为 RDB_64BITLEN,表示长度值为 64 位的非编码值:
//通过读取接下来的 8 个字节,将这 8 个字节解析为一个 64 位的长度值,进行网络字节序的转换(ntohu64)
} else {
rdbReportCorruptRDB(
"Unknown length encoding %d in rdbLoadLen()",type);
return -1; /* Never reached. */
//如果无法识别编码类型 rdbReportCorruptRDB报告 RDB 文件的损坏。
}
return 0;
}
总的来说
读取RDB文件中的一个长度值,并根据不同的编码方式进行解析。
5.RDB_OPCODE_EOF
else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
//表示已经读取到了 RDB 文件的结尾
break;
}
6.RDB_OPCODE_SELECTDB
else if (type == RDB_OPCODE_SELECTDB) {
/* SELECTDB: Select the specified database. */
/*SELECTDB:选择指定的数据库*/
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
selected_dbid = dbid;
continue; /* Read type again. */
//表示切换数据库的操作。首先通过 rdbLoadLen 函数读取数据库的 ID,并检查读取是否出错。然后记录切换的数据库 ID 再continue
}
---------------------------------------------------------------------------------------------------------------------------------
介绍一下这里的rdbCheckInfo
/* Print information during RDB checking. */
void rdbCheckInfo(const char *fmt, ...) {
char msg[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
printf("[offset %llu] %s\n",
(unsigned long long) (rdbstate.rio ?
rdbstate.rio->processed_bytes : 0), msg);
//是用于获取偏移量的表达式。访问 rdbstate.rio->processed_bytes 获取已处理的字节数
}
打印偏移量和信息
7. RDB_OPCODE_RESIZEDB
else if (type == RDB_OPCODE_RESIZEDB) {
/* RESIZEDB: Hint about the size of the keys in the currently
* selected data base, in order to avoid useless rehashing. */
/*RESIZEDB:关于当前
*选定的数据库,以避免无用的重新哈希*/
uint64_t db_size, expires_size;
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
if ((db_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
if ((expires_size = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
goto eoferr;
continue; /* Read type again. */
}
表示扩展数据库的操作。通过 rdbLoadLen 函数读取数据库大小和过期键大小
8.RDB_OPCODE_AUX
else if (type == RDB_OPCODE_AUX) {
/* AUX: generic string-string fields. Use to add state to RDB
* which is backward compatible. Implementations of RDB loading
* are required to skip AUX fields they don't understand.
*
* An AUX field is composed of two strings: key and value. */
/*AUX:通用字符串字段。用于将状态添加到RDB
*这是向后兼容的。RDB加载的实现
*被要求跳过他们不理解的AUX字段。
*
*AUX字段由两个字符串组成:键和值*/
robj *auxkey, *auxval;
rdbstate.doing = RDB_CHECK_DOING_READ_AUX;
if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) {
decrRefCount(auxkey);
goto eoferr;
}
rdbCheckInfo("AUX FIELD %s = '%s'",
(char*)auxkey->ptr, (char*)auxval->ptr);
decrRefCount(auxkey);
// 减少对象的引用计数(reference count)。
decrRefCount(auxval);
continue; /* Read type again. */
//表示辅助信息的数据段。通过 rdbLoadStringObject 函数分别读取辅助信息的键和值,并检查读取是否出错。
}
官方这里写的AUX是向后兼容的 我看过redisshake源码 雀氏少了很多类型 可以注意一下
也不影响读取
-------------------------------------------------------------------------------------------------------------------------------
介绍一下 这里的rdbGenericLoadStringObject
plain
代表是否以原始形式加载整数。
load_sds
是否将整数加载为SDS(Redis特有的用来处理字符串的数据结构)。
void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
int plain = flags & RDB_LOAD_PLAIN;
//通过位运算将 flags 中的 RDB_LOAD_PLAIN 标志提取出来
int sds = flags & RDB_LOAD_SDS;
//通过位运算将 flags 中的 RDB_LOAD_SDS 标志提取出来
int isencoded;
unsigned long long len;
len = rdbLoadLen(rdb,&isencoded);
// 调用函数 rdbLoadLen 从 RDB 文件中加载长度值,并将加载的结果存储到 len 变量中
if (len == RDB_LENERR) return NULL;
//如果加载长度值出现错误(如返回值为 RDB_LENERR),则返回 NULL。
if (isencoded) {
switch(len) {
case RDB_ENC_INT8:
case RDB_ENC_INT16:
case RDB_ENC_INT32:
return rdbLoadIntegerObject(rdb,len,flags,lenptr);
//如果编码类型为 RDB_ENC_INT8、RDB_ENC_INT16 或 RDB_ENC_INT32,则调用 rdbLoadIntegerObject 函数加载整数对象,并返回结果。
case RDB_ENC_LZF:
return rdbLoadLzfStringObject(rdb,flags,lenptr);
//如果编码类型为 RDB_ENC_LZF,则调用 rdbLoadLzfStringObject 函数加载 LZF 压缩字符串对象
default:
rdbReportCorruptRDB("Unknown RDB string encoding type %llu",len);
return NULL;
}
}
if (plain || sds) {
void *buf = plain ? ztrymalloc(len) : sdstrynewlen(SDS_NOINIT,len);
//如果 plain 为真,表示需要以普通字节数组的形式加载字符串对象
if (!buf) {
serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len);
return NULL;
}
if (lenptr) *lenptr = len;
if (len && rioRead(rdb,buf,len) == 0) {
if (plain)
zfree(buf);
else
sdsfree(buf);
return NULL;
}
return buf;
} else {
robj *o = tryCreateStringObject(SDS_NOINIT,len);
//则调用 tryCreateStringObject 函数创建普通字符串对象
if (!o) {
serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbGenericLoadStringObject failed allocating %llu bytes", len);
return NULL;
}
if (len && rioRead(rdb,o->ptr,len) == 0) {
decrRefCount(o);
return NULL;
}
return o;
}
}
根据类型创建字符串
如
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
int plain = flags & RDB_LOAD_PLAIN;
int sds = flags & RDB_LOAD_SDS;
int encode = flags & RDB_LOAD_ENC;
unsigned char enc[4];
long long val;
if (enctype == RDB_ENC_INT8) {
if (rioRead(rdb,enc,1) == 0) return NULL;
val = (signed char)enc[0];
} else if (enctype == RDB_ENC_INT16) {
uint16_t v;
if (rioRead(rdb,enc,2) == 0) return NULL;
v = ((uint32_t)enc[0])|
((uint32_t)enc[1]<<8);
val = (int16_t)v;
} else if (enctype == RDB_ENC_INT32) {
uint32_t v;
if (rioRead(rdb,enc,4) == 0) return NULL;
v = ((uint32_t)enc[0])|
((uint32_t)enc[1]<<8)|
((uint32_t)enc[2]<<16)|
((uint32_t)enc[3]<<24);
val = (int32_t)v;
}//如果给定的 enctype 不属于上述三种编码类型
else {
rdbReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
return NULL; /* Never reached. */
}
if (plain || sds) {
char buf[LONG_STR_SIZE], *p;
int len = ll2string(buf,sizeof(buf),val);
//用于将一个长整型数值 svalue 转换为字符串
if (lenptr) *lenptr = len;
p = plain ? zmalloc(len) : sdsnewlen(SDS_NOINIT,len);
memcpy(p,buf,len);
return p;
//如果 plain 标志位或 sds 标志位被设置,那么函数将整数值 val 转换为字符串,并根据标志位分配不同//的内存来存储字符串
} else if (encode) {
return createStringObjectFromLongLongForValue(val);
} else {
return createObject(OBJ_STRING,sdsfromlonglong(val));
}
}
函数的主要功能是根据给定的编码类型从输入流 rdb
中读取相应字节数的数据,并将其转换为相应的整数值。
这里的createObject
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution), or
* alternatively the LFU counter. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
return o;
}
会设置对象的 LRU 属性为 LFU 时间或 LRU 时钟值
再看一下它是怎么解析LZF字符串
/* Load an LZF compressed string in RDB format. The returned value
* changes according to 'flags'. For more info check the
* rdbGenericLoadStringObject() function. */
void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr) {
int plain = flags & RDB_LOAD_PLAIN;
int sds = flags & RDB_LOAD_SDS;
uint64_t len, clen;
unsigned char *c = NULL;
char *val = NULL;
if ((clen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
//得到压缩后和压缩前的字符串长度clen和len。
if ((c = ztrymalloc(clen)) == NULL) {
serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)clen);
goto err;
}
/* Allocate our target according to the uncompressed size. */
if (plain) {
val = ztrymalloc(len);
} else {
val = sdstrynewlen(SDS_NOINIT,len);
}
if (!val) {
serverLog(isRestoreContext()? LL_VERBOSE: LL_WARNING, "rdbLoadLzfStringObject failed allocating %llu bytes", (unsigned long long)len);
goto err;
}
if (lenptr) *lenptr = len;
/* Load the compressed representation and uncompress it to target. */
if (rioRead(rdb,c,clen) == 0) goto err;
if (lzf_decompress(c,clen,val,len) != len) {
rdbReportCorruptRDB("Invalid LZF compressed string");
goto err;
从RDB中读取压缩数据到c数组,然后将其解压缩到目标字符串val中。
}
zfree(c);
if (plain || sds) {
return val;
} else {
return createObject(OBJ_STRING,val);
}
err:
zfree(c);
if (plain)
zfree(val);
else
sdsfree(val);
return NULL;
}
最后就是减少计数的函数
void decrRefCount(robj *o) {
if (o->refcount == 1) {
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
default: serverPanic("Unknown object type"); break;
}
zfree(o);
} else {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
}
}
没什么好说的 看了就懂
9.RDB_OPCODE_MODULE_AUX
else if (type == RDB_OPCODE_MODULE_AUX) {
/* AUX: Auxiliary data for modules. */
uint64_t moduleid, when_opcode, when;
rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX;
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
rdbCheckError("bad when_opcode");
goto err;
}
//表示模块辅助信息的数据段。通过 rdbLoadLen 函数读取模块 信息,并检查读取是否出错。
char name[10];
moduleTypeNameByID(name,moduleid);
rdbCheckInfo("MODULE AUX for: %s", name);
robj *o = rdbLoadCheckModuleValue(&rdb,name);
decrRefCount(o);
continue; /* Read type again. */
//moduleTypeNameByID 函数根据模块 ID 获取模块的名称,然后打印出模块的名称作为辅助信息。接着使用 rdbLoadCheckModuleValue 函数加载模块的数据值
}
我对redis模块不是很熟悉 大致看看功能吧
/* Turn an (unresolved) module ID into a type name, to show the user an
* error when RDB files contain module data we can't load.
* The buffer pointed by 'name' must be 10 bytes at least. The function will
* fill it with a null terminated module name. */
/*将(未解析的)模块ID转换为类型名称,以向用户显示
*当RDB文件包含我们无法加载的模块数据时出错。
*“name”指向的缓冲区必须至少为10个字节。该功能将
*用以null结尾的模块名称填充它*/
void moduleTypeNameByID(char *name, uint64_t moduleid) {
const char *cset = ModuleTypeNameCharSet;
name[9] = '\0';
char *p = name+8;
moduleid >>= 10;
//进行右移 10 位操作,因为这些位不包含模块类型信息
for (int j = 0; j < 9; j++) {
*p-- = cset[moduleid & 63];
moduleid >>= 6;
//高位到低位依次将模块类型字符设置到 p 指针指向的位置上
}
}
* This function is called by rdbLoadObject() when the code is in RDB-check
* mode and we find a module value of type 2 that can be parsed without
* the need of the actual module. The value is parsed for errors, finally
* a dummy redis object is returned just to conform to the API. */
/*当代码处于RDB检查中时,rdbLoadObject()调用此函数
*模式,我们找到了一个类型为2的模块值,该值可以在没有
*实际模块的需要。最后对值进行错误分析
*返回一个伪redis对象只是为了符合API*/
robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
uint64_t opcode;
//在循环体中,根据不同的opcode,会尝试读取不同类型的数据,并进行错误处理
while((opcode = rdbLoadLen(rdb,NULL)) != RDB_MODULE_OPCODE_EOF) {
if (opcode == RDB_MODULE_OPCODE_SINT ||
opcode == RDB_MODULE_OPCODE_UINT)
{
uint64_t len;
if (rdbLoadLenByRef(rdb,NULL,&len) == -1) {
rdbReportCorruptRDB(
"Error reading integer from module %s value", modulename);
}
} else if (opcode == RDB_MODULE_OPCODE_STRING) {
robj *o = rdbGenericLoadStringObject(rdb,RDB_LOAD_NONE,NULL);
if (o == NULL) {
rdbReportCorruptRDB(
"Error reading string from module %s value", modulename);
}
decrRefCount(o);
} else if (opcode == RDB_MODULE_OPCODE_FLOAT) {
float val;
if (rdbLoadBinaryFloatValue(rdb,&val) == -1) {
rdbReportCorruptRDB(
"Error reading float from module %s value", modulename);
}
} else if (opcode == RDB_MODULE_OPCODE_DOUBLE) {
double val;
if (rdbLoadBinaryDoubleValue(rdb,&val) == -1) {
rdbReportCorruptRDB(
"Error reading double from module %s value", modulename);
}
}
}
return createStringObject("module-dummy-value",18);
}
10.RDB_OPCODE_FUNCTION_PRE_GA
else if (type == RDB_OPCODE_FUNCTION_PRE_GA) {
rdbCheckError("Pre-release function format not supported %d",rdbver);
goto err;
//表示不支持旧版本的函数格式,打印相应的错误信息并跳转到 err 错误处理标签。
}
11.RDB_OPCODE_FUNCTION2
else if (type == RDB_OPCODE_FUNCTION2) {
sds err = NULL;
rdbstate.doing = RDB_CHECK_DOING_READ_FUNCTIONS;
if (rdbFunctionLoad(&rdb, rdbver, NULL, 0, &err) != C_OK) {
rdbCheckError("Failed loading library, %s", err);
sdsfree(err);
goto err;
}
continue;
//表示加载函数库的操作。
//调用 rdbFunctionLoad 函数加载函数库并进行错误检查。
//如果加载失败,打印相应的错误信息
}
我对redis函数库不是很熟悉 也大致看看功能吧
/* Save the given functions_ctx to the rdb.
* The err output parameter is optional and will be set with relevant error
* message on failure, it is the caller responsibility to free the error
* message on failure.
*
* The lib_ctx argument is also optional. If NULL is given, only verify rdb
* structure with out performing the actual functions loading. */
/*将给定的functions_ctx保存到rdb中。
* err输出参数是可选的,将设置相关错误
* 消息失败时,由调用者负责释放错误
* 失败消息。
*
* lib_ctx参数也是可选的。如果给定NULL,则仅验证rdb
* 不执行实际功能加载的结构*/
int rdbFunctionLoad(rio *rdb, int ver, functionsLibCtx* lib_ctx, int rdbflags, sds *err) {
UNUSED(ver);
sds error = NULL;
sds final_payload = NULL;
int res = C_ERR;
//通过rdbGenericLoadStringObject从RDB中加载函数库的payload(实际数据
if (!(final_payload = rdbGenericLoadStringObject(rdb, RDB_LOAD_SDS, NULL))) {
error = sdsnew("Failed loading library payload");
goto done;
}
if (lib_ctx) {
sds library_name = NULL;
if (!(library_name = functionsCreateWithLibraryCtx(final_payload, rdbflags & RDBFLAGS_ALLOW_DUP, &error, lib_ctx)))
//functionsCreateWithLibraryCtx创建函数库,并且这会将final_payload中的内容设置到函数库
{
if (!error) {
error = sdsnew("Failed creating the library");
}
goto done;
}
sdsfree(library_name);
}
//没有执行goto done,表示函数库加载成功
res = C_OK;
done:
if (final_payload) sdsfree(final_payload);
if (error) {
if (err) {
*err = error;
} else {
serverLog(LL_WARNING, "Failed creating function, %s", error);
sdsfree(error);
}
}
return res;
}
* Compile and save the given library, return the loaded library name on success
* and NULL on failure. In case on failure the err out param is set with relevant error message */
/*编译并保存给定的库,成功时返回加载的库名称
* 失败时为NULL。如果出现故障,则会使用相关的错误消息设置err - out参数 */
sds functionsCreateWithLibraryCtx(sds code, int replace, sds* err, functionsLibCtx *lib_ctx) {
dictIterator *iter = NULL;
dictEntry *entry = NULL;
functionLibInfo *new_li = NULL;
functionLibInfo *old_li = NULL;
functionsLibMataData md = {0};
if (functionExtractLibMetaData(code, &md, err) != C_OK) {
//functionExtractLibMetaData 使用给定的代码提取库的元数据,存在 md 变量中
return NULL;
}
if (functionsVerifyName(md.name)) {
//首先检查库名称是否满足要求。
*err = sdsnew("Library names can only contain letters, numbers, or underscores(_) and must be at least one character long");
goto error;
}
engineInfo *ei = dictFetchValue(engines, md.engine);
//使用库引擎名称从字典 engines 中获取相应的引擎信息。
if (!ei) {
*err = sdscatfmt(sdsempty(), "Engine '%S' not found", md.engine);
goto error;
}
engine *engine = ei->engine;
old_li = dictFetchValue(lib_ctx->libraries, md.name);
//从函数库上下文的字典 libraries 中获取具有给定名称的旧的函数库信息,存在 old_li 变量中
if (old_li && !replace) {
old_li = NULL;
*err = sdscatfmt(sdsempty(), "Library '%S' already exists", md.name);
goto error;
}
if (old_li) {
libraryUnlink(lib_ctx, old_li);
}
new_li = engineLibraryCreate(md.name, ei, code);
//使用库名称、引擎和code创建一个新的函数库信息,存在 new_li 变量中
if (engine->create(engine->engine_ctx, new_li, md.code, err) != C_OK) {
goto error;
}
if (dictSize(new_li->functions) == 0) {
//检查新的函数库中是否注册了至少一个函数
*err = sdsnew("No functions registered");
goto error;
}
/* Verify no duplicate functions */
iter = dictGetIterator(new_li->functions);
//遍历新的函数库中的每个函数,检查是否存在重复的函数名称
while ((entry = dictNext(iter))) {
functionInfo *fi = dictGetVal(entry);
if (dictFetchValue(lib_ctx->functions, fi->name)) {
/* functions name collision, abort. */
*err = sdscatfmt(sdsempty(), "Function %s already exists", fi->name);
goto error;
}
}
dictReleaseIterator(iter);
iter = NULL;
libraryLink(lib_ctx, new_li);
//将新的函数库与函数库上下文关联起来
if (old_li) {
engineLibraryFree(old_li);
}
//如果存在旧的函数库信息 先释放
sds loaded_lib_name = md.name;
md.name = NULL;
//将成功加载的库名称存储在 loaded_lib_name 变量中
functionFreeLibMetaData(&md);
return loaded_lib_name;
error:
if (iter) dictReleaseIterator(iter);
if (new_li) engineLibraryFree(new_li);
if (old_li) libraryLink(lib_ctx, old_li);
functionFreeLibMetaData(&md);
return NULL;
}
12.无效的类型
else {
if (!rdbIsObjectType(type)) {
rdbCheckError("Invalid object type: %d", type);
goto err;
}
rdbstate.key_type = type;
}
五.如果不是上述的类型 就要读取键值对
/* Read key */
rdbstate.doing = RDB_CHECK_DOING_READ_KEY;
//rdbstate.doing 的值为 RDB_CHECK_DOING_READ_KEY,表示当前正在读取键。
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
rdbstate.key = key;
rdbstate.keys++;
// rdbLoadStringObject 函数读取键,并检查是否读取出错
// c.将读取到的键赋值给 rdbstate.key。
// d.增加键计数器 rdbstate.keys。
/* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
//rdbstate.doing 的值为 RDB_CHECK_DOING_READ_OBJECT_VALUE,表示当前正在读取值。
if ((val = rdbLoadObject(type,&rdb,key->ptr,selected_dbid,NULL)) == NULL) goto eoferr;
//使用 rdbLoadObject 函数加载值,并进行相应的错误检查
/* Check if the key already expired. */
if (expiretime != -1 && expiretime < now)
rdbstate.already_expired++;
if (expiretime != -1) rdbstate.expires++;
rdbstate.key = NULL;
decrRefCount(key);
decrRefCount(val);
//减少引用计数
rdbstate.key_type = -1;
expiretime = -1;
/*检查键是否已经过期。如果过期时间 expiretime 小于当前时间 now,表示键已过期,增加已过期计数器 rdbstate.already_expired。如果过期时间不为 - 1,表示键设置了过期时间,增加过期计数器 rdbstate.expires*/
}
总的来说就是 从RDB文件中连续读取键和相应的值,并进行相应的处理,包括记录读取的键数量、检查键是否过期 然后继续下一个循环
六.对RDB文件进行校验和
/* Verify the checksum if RDB version is >= 5 */
if (rdbver >= 5 && server.rdb_checksum) {
uint64_t cksum, expected = rdb.cksum;
//如果 RDB 版本号大于等于 5 并且服务器开启了 RDB 校验和功能
rdbstate.doing = RDB_CHECK_DOING_CHECK_SUM;
if (rioRead(&rdb,&cksum,8) == 0) goto eoferr;
memrev64ifbe(&cksum);//对校验和进行大小端转换
//表示 RDB 文件是在禁用校验和的情况下保存的,打印相应的信息
if (cksum == 0) {
rdbCheckInfo("RDB file was saved with checksum disabled: no check performed.");
} else if (cksum != expected) {
//校验和与预期值不匹配,表示校验和验证失败
rdbCheckError("RDB CRC error");
goto err;
} else {
rdbCheckInfo("Checksum OK");
}
}
if (closefile) fclose(fp);
stopLoading(1);
return 0;
//closefile 变量决定是否关闭文件指针,并调用 stopLoading 函数以结束加载过程。如果加载过程中发生错误,返回 1;否则返回 0。
就是在读取RDB文件后,对RDB文件进行校验和的验证,并根据验证结果打印相应的信息或执行相应的错误处理操作