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

18 篇文章 0 订阅
17 篇文章 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] = '\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

  1. 读取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;
     }
    
  2. 读取value:内容太多了,放下一节吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值