博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
redis源码剖析(十二)—— RDB持久化
阅读量:2491 次
发布时间:2019-05-11

本文共 10303 字,大约阅读时间需要 34 分钟。

文章目录

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

RBD文件载入

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

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

RDB文件分析

[root@python redis-4.0.14]# od -c dump.rdb0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e0000060 302 212  \b 331   ] 372  \b   u   s   e   d   -   m   e   m 3020000100 210  \0  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 3000000140   { 377   8 033   _ 360   I 223 254 3430000152[root@python redis-4.0.14]# redis-cli127.0.0.1:6379> flushallOK127.0.0.1:6379> set name xxxxOK127.0.0.1:6379> saveOK127.0.0.1:6379> exit[root@python redis-4.0.14]# od -c dump.rdb0000000   R   E   D   I   S   0   0   0   8 372  \t   r   e   d   i   s0000020   -   v   e   r 006   4   .   0   .   1   4 372  \n   r   e   d0000040   i   s   -   b   i   t   s 300   @ 372 005   c   t   i   m   e0000060 302   b   | 333   ] 372  \b   u   s   e   d   -   m   e   m 3020000100   0 356  \f  \0 372  \f   a   o   f   -   p   r   e   a   m   b0000120   l   e 300  \0 376  \0 373 001  \0  \0 004   n   a   m   e 0040000140   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

转载地址:http://nkorb.baihongyu.com/

你可能感兴趣的文章
mui 结束时间不能大于开始时间
查看>>
SQL创建表格——手写代码
查看>>
1、Canvas的基本用法
查看>>
两个链表的第一个公共结点-输入两个链表,找出它们的第一个公共结点。
查看>>
Swagger+AutoRest 生成web api客户端(.Net)
查看>>
setTimeout详解
查看>>
Nginx配置指定媒体类型文件强制下载
查看>>
gdb命令中attach使用
查看>>
Koa2 静态服务及代理配置
查看>>
网络运维调查报告
查看>>
bat-bat-bat (重要的事情说三遍)
查看>>
算法题11 字符串的所有对称子串
查看>>
bzoj1058: [ZJOI2007]报表统计
查看>>
寒假作业01
查看>>
关于“using namespace std”
查看>>
安卓模拟器bluestacks mac地址修改教程
查看>>
(转)android技巧01:Preferencescreen中利用intent跳转activity
查看>>
Beta Daily Scrum 第七天
查看>>
jq-dom操作
查看>>
Android style 继承
查看>>