redis source code analysis (XII) - RDB persistence

To avoid data loss. Redis save data to disk, avoiding accidental loss of data.

RBD files are loaded

When redis start to detect whether there rdb file, any, will be automatically loaded.

command effect
save Blocking server processes, know rbd file is created
bgsave fork child process, the child process is responsible for creating RDB file

RDB file analysis

 [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
  • Redis. 8 0 0 0 0000000 372 \ tredis
    . 1, five byte Redis
    2, four-byte version
    3, a constant eof byte
    4, eight-byte checksum

  • 0.00014 million XXXX name 004 377 004 314 "221 277 [223,026 $
    004 key length of
    377 314" 221 277 [$ 223,026 eight-byte long checksum

Source code analysis

Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success
will be saved to the database on disk.
Save successful return REDIS_OK, error / failure to return 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;
}

Core code

// 遍历所有数据库
    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);
    }
  • Get the key and key
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 file write

 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 write key function 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
Published 257 original articles · won praise 223 · views 320 000 +

Guess you like

Origin blog.csdn.net/csdn_kou/article/details/103400049