Redis是怎样检查RDB的?

Redis有检查RDB文件的功能 用来看RDB是否正常

主要实现 在

int redis_check_rdb(char *rdbfilename, FILE *fp)

这个函数 现在详细介绍一下

目录

一.声明了一些变量

二.检查文件状态并初始化一些状态

三.读取开头 看是不是RDB格式

四.正式读取

1.RDB_OPCODE_EXPIRETIME

2. RDB_OPCODE_EXPIRETIME_MS

3.RDB_OPCODE_FREQ

4.RDB_OPCODE_IDLE

5.RDB_OPCODE_EOF

6.RDB_OPCODE_SELECTDB

7. RDB_OPCODE_RESIZEDB

8.RDB_OPCODE_AUX

9.RDB_OPCODE_MODULE_AUX

10.RDB_OPCODE_FUNCTION_PRE_GA

11.RDB_OPCODE_FUNCTION2

12.无效的类型

五.如果不是上述的类型 就要读取键值对

六.对RDB文件进行校验和


一.声明了一些变量

 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文件进行校验和的验证,并根据验证结果打印相应的信息或执行相应的错误处理操作

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值