redis源码剖析(十二)—— RDB持久化

为避免数据丢失。将redis中的数据保存到磁盘中,避免数据意外丢失。

RBD文件载入

在redis启动时检测是否有rdb文件,有的话会自动载入。

命令作用
save阻塞服务器进程,知道rbd文件创建完成
bgsavefork子进程,由子进程负责创建RDB文件

RDB文件分析

 [root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302 212  \b 331   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100 210  \0  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 300
0000140   { 377   8 033   _ 360   I 223 254 343
0000152
[root@python redis-4.0.14]# redis-cli
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set name xxxx
OK
127.0.0.1:6379> save
OK
127.0.0.1:6379> exit
[root@python redis-4.0.14]# od -c dump.rdb
0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s
0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d
0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e
0000060 302   b   | 333   ] 372  \b   u   s   e   d   -   m   e   m 302
0000100   0 356  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b
0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 004
0000140   x   x   x   x 377 314   " 221 277   [ 223 026   $
0000155
  • 0000000 R E D I S 0 0 0 8 372 \t r e d i s
    1、五个字节的REDIS
    2、四个字节版本号
    3、 一个字节的eof常量
    4、八个字节校验和

  • 004 n a m e 004 0000140 x x x x 377 314 " 221 277 [ 223 026 $
    004是key的长度
    377 314 " 221 277 [ 223 026 $ 八字节长的校验和

源码分析

Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success
将数据库保存到磁盘上。
保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。

int rdbSave(char *filename) {
    // 创建临时文件
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }

    // 初始化 I/O
    rioInitWithFile(&rdb,fp);

    // 设置校验和函数
    if (server.rdb_checksum)
        rdb.update_cksum = rioGenericUpdateChecksum;

    // 写入 RDB 版本号
    snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
    if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;

    // 遍历所有数据库
    for (j = 0; j < server.dbnum; j++) {

        // 指向数据库
        redisDb *db = server.db+j;

        // 指向数据库键空间
        dict *d = db->dict;

        // 跳过空数据库
        if (dictSize(d) == 0) continue;

        // 创建键空间迭代器
        di = dictGetSafeIterator(d);
        if (!di) {
            fclose(fp);
            return REDIS_ERR;
        }

        /* Write the SELECT DB opcode
         *
         * 写入 DB 选择器
         */
        if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
        if (rdbSaveLen(&rdb,j) == -1) goto werr;

        /* Iterate this DB writing every entry
         *
         * 遍历数据库,并写入每个键值对的数据
         */
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            // 根据 keystr ,在栈中创建一个 key 对象
            initStaticStringObject(key,keystr);

            // 获取键的过期时间
            expire = getExpire(db,&key);

            // 保存键值对数据
            if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */

    /* EOF opcode
     *
     * 写入 EOF 代码
     */
    if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
     * loading code skips the check in this case.
     *
     * CRC64 校验和。
     *
     * 如果校验和功能已关闭,那么 rdb.cksum 将为 0 ,
     * 在这种情况下, RDB 载入时会跳过校验和检查。
     */
    cksum = rdb.cksum;
    memrev64ifbe(&cksum);
    rioWrite(&rdb,&cksum,8);

    /* Make sure data will not remain on the OS's output buffers */
    // 冲洗缓存,确保数据已写入磁盘
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok.
     *
     * 使用 RENAME ,原子性地对临时文件进行改名,覆盖原来的 RDB 文件。
     */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }

    // 写入完成,打印日志
    redisLog(REDIS_NOTICE,"DB saved on disk");

    // 清零数据库脏状态
    server.dirty = 0;

    // 记录最后一次完成 SAVE 的时间
    server.lastsave = time(NULL);

    // 记录最后一次执行 SAVE 的状态
    server.lastbgsave_status = REDIS_OK;

    return REDIS_OK;

werr:
    // 关闭文件
    fclose(fp);
    // 删除文件
    unlink(tmpfile);

    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));

    if (di) dictReleaseIterator(di);

    return REDIS_ERR;
}

核心代码

// 遍历所有数据库
    for (j = 0; j < server.dbnum; j++) {
        // 创建键空间迭代器
        di = dictGetSafeIterator(d);
        if (!di) {
            fclose(fp);
            return REDIS_ERR;
        }

        /* Write the SELECT DB opcode
         *
         * 写入 DB 选择器
         */
         /*  
         * 遍历数据库,并写入每个键值对的数据
         */
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            // 根据 keystr ,在栈中创建一个 key 对象
            initStaticStringObject(key,keystr);

            // 获取键的过期时间
            expire = getExpire(db,&key);

            // 保存键值对数据
            if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
        }
        dictReleaseIterator(di);
    }
  • 获取键值和键
237 // 计算给定键的哈希值
238 #define dictHashKey(d, key) (d)->type->hashFunction(key)
239 // 返回获取给定节点的键
240 #define dictGetKey(he) ((he)->key)
241 // 返回获取给定节点的值
242 #define dictGetVal(he) ((he)->v.val)

rdb文件写入

 891 int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
 892                         long long expiretime, long long now)
 893 {
 894     /* Save the expire time
 895      *
 896      * 保存键的过期时间
 897      */
 898     if (expiretime != -1) {
 899         /* If this key is already expired skip it
 900          *
 901          * 不写入已经过期的键
 902          */
 903         if (expiretime < now) return 0;
 904
 905         if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
 906         if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
 907     }
 908
 909     /* Save type, key, value
 910      *
 911      * 保存类型,键,值
 912      */
 913     if (rdbSaveObjectType(rdb,val) == -1) return -1;
 914     if (rdbSaveStringObject(rdb,key) == -1) return -1;
 915     if (rdbSaveObject(rdb,val) == -1) return -1;
 916
 917     return 1;
 918 }

rdb写入关键函数rdbSaveObjectType

 655 /* Save the object type of object "o".
 656  *
 657  * 将对象 o 的类型写入到 rdb 中
 658  */
 659 int rdbSaveObjectType(rio *rdb, robj *o) {
 660
 661     switch (o->type) {
 662
 663     case REDIS_STRING:
 664         return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
 665
 666     case REDIS_LIST:
 667         if (o->encoding == REDIS_ENCODING_ZIPLIST)
 668             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
 669         else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
 670             return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
 671         else
 672             redisPanic("Unknown list encoding");
 673
 674     case REDIS_SET:
 675         if (o->encoding == REDIS_ENCODING_INTSET)
 676             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);
 677         else if (o->encoding == REDIS_ENCODING_HT)
 678             return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);
 679         else
 680             redisPanic("Unknown set encoding");
 681
 682     case REDIS_ZSET:
 683         if (o->encoding == REDIS_ENCODING_ZIPLIST)
 684             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);
 685         else if (o->encoding == REDIS_ENCODING_SKIPLIST)
 686             return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);
 687         else
 688             redisPanic("Unknown sorted set encoding");
 689
 690     case REDIS_HASH:
 691         if (o->encoding == REDIS_ENCODING_ZIPLIST)
 692             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);
 693         else if (o->encoding == REDIS_ENCODING_HT)
 694             return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);
 695         else
 696             redisPanic("Unknown hash encoding");
 697
 698     default:
 699         redisPanic("Unknown object type");
 700     }
 701
 702     return -1; /* avoid warning */
 703 }

rdbSaveStringObjectRaw

 493 /* Like rdbSaveStringObjectRaw() but handle encoded objects */
 494 /*
 495  * 将给定的字符串对象 obj 保存到 rdb 中。
 496  *
 497  * 函数返回 rdb 保存字符串对象所需的字节数。
 498  *
 499  * p.s. 代码原本的注释 rdbSaveStringObjectRaw() 函数已经不存在了。
 500  */
 501 int rdbSaveStringObject(rio *rdb, robj *obj) {
 502
 503     /* Avoid to decode the object, then encode it again, if the
 504      * object is already integer encoded. */
 505     // 尝试对 INT 编码的字符串进行特殊编码
 506     if (obj->encoding == REDIS_ENCODING_INT) {
 507         return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
 508
 509     // 保存 STRING 编码的字符串
 510     } else {
 511         redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
 512         return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
 513     }
 514 }
 515

rdbSaveLongLongAsStringObject

 453 /* Save a long long value as either an encoded string or a string.
 454  *
 455  * 将输入的 long long 类型的 value 转换成一个特殊编码的字符串,
 456  * 或者是一个普通的字符串表示的整数,
 457  * 然后将它写入到 rdb 中。
 458  *
 459  * 函数返回在 rdb 中保存 value 所需的字节数。
 460  */
 461 int rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
 462     unsigned char buf[32];
 463     int n, nwritten = 0;
 464
 465     // 尝试以节省空间的方式编码整数值 value
 466     int enclen = rdbEncodeInteger(value,buf);
 467
 468     // 编码成功,直接写入编码后的缓存
 469     // 比如,值 1 可以编码为 11 00 0001
 470     if (enclen > 0) {
 471         return rdbWriteRaw(rdb,buf,enclen);
 472
 473     // 编码失败,将整数值转换成对应的字符串来保存
 474     // 比如,值 999999999 要编码成 "999999999" ,
 475     // 因为这个值没办法用节省空间的方式编码
 476     } else {
 477         /* Encode as string */
 478         // 转换成字符串表示
 479         enclen = ll2string((char*)buf,32,value);
 480         redisAssert(enclen < 32);
 481         // 写入字符串长度
 482         if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;
 483         nwritten += n;
 484         // 写入字符串
 485         if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;
 486         nwritten += n;
 487     }
 488
 489     // 返回长度
 490     return nwritten;
 491 }
 492
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值