美文网首页redis
redis rdb持久化

redis rdb持久化

作者: 晴天哥_王志 | 来源:发表于2018-06-19 22:30 被阅读33次

    redis rdb和aof持久化的区别

     关于这两者的区别,网上有很多资料,这里我只想补充下自己理解的两个比较核心的点:

    • 持久化过程是否异步,rdb持久化是后台异步进程执行,aof是同步执行
    • 持久化内容格式,rdb是直接存储实际内存存储数据,aof是转为redis执行命令行存储

    redis rdb持久化过程

     分析redis的rdb持久化过程直接从bgsaveCommand命令的执行过程开始分析

    • 首先不能同时执行多个bgsave命令或同时执行bgrewriteaof命令
    • 其次进入后台fork线程生成rdb文件过程rdbSaveBackground
    void bgsaveCommand(redisClient *c) {
    
        // 不能重复执行 BGSAVE
        if (server.rdb_child_pid != -1) {
            addReplyError(c,"Background save already in progress");
    
        // 不能在 BGREWRITEAOF 正在运行时执行
        } else if (server.aof_child_pid != -1) {
            addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");
    
        // 执行 BGSAVE
        } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) {
            addReplyStatus(c,"Background saving started");
    
        } else {
            addReply(c,shared.err);
        }
    }
    



     在rdbSaveBackground内部执行了fork子进程开始进行rdb的持久化操作,核心逻辑在执行rdbSave(filename)的命令。

    int rdbSaveBackground(char *filename) {
        pid_t childpid;
        long long start;
    
        // 如果 BGSAVE 已经在执行,那么出错
        if (server.rdb_child_pid != -1) return REDIS_ERR;
    
        // 记录 BGSAVE 执行前的数据库被修改次数
        server.dirty_before_bgsave = server.dirty;
    
        // 最近一次尝试执行 BGSAVE 的时间
        server.lastbgsave_try = time(NULL);
    
        // fork() 开始前的时间,记录 fork() 返回耗时用
        start = ustime();
    
        if ((childpid = fork()) == 0) {
            int retval;
    
            /* Child */
    
            // 关闭网络连接 fd
            closeListeningSockets(0);
    
            // 设置进程的标题,方便识别
            redisSetProcTitle("redis-rdb-bgsave");
    
            // 执行保存操作
            retval = rdbSave(filename);
    
            // 打印 copy-on-write 时使用的内存数
            if (retval == REDIS_OK) {
                size_t private_dirty = zmalloc_get_private_dirty();
    
                if (private_dirty) {
                    redisLog(REDIS_NOTICE,
                        "RDB: %zu MB of memory used by copy-on-write",
                        private_dirty/(1024*1024));
                }
            }
    
            // 向父进程发送信号
            exitFromChild((retval == REDIS_OK) ? 0 : 1);
    
        } 
    
        // 省略非核心的逻辑
        return REDIS_OK;
    }
    



     整个生成rdb文件的核心,整体逻辑如下

    • 创建rdb磁盘文件
    • 遍历redis的所有db进行写入

    整个写入数据是将redis内存中的数据原封不动的写入到rdb文件当中,整个写入过程按照以下顺序进行执行:

    • 通过rdbSaveType方法写入type
    • 通过rdbSaveLen写入数据(或者是下面的集中替代)
    • 通过rdbSaveObjectType存储redis value的数据类型
    • 通过rdbSaveStringObject存储redis key的数据
    • 通过rdbSaveObject存储redis value的数据

    整个过程中我们发现redis就是把实际内存数据库的数据dump到rdb文件当中

    /* 
     * 将数据库保存到磁盘上。
     *
     * 保存成功返回 REDIS_OK ,出错/失败返回 REDIS_ERR 。
     */
    int rdbSave(char *filename) {
        dictIterator *di = NULL;
        dictEntry *de;
        char tmpfile[256];
        char magic[10];
        int j;
        long long now = mstime();
        FILE *fp;
        rio rdb;
        uint64_t cksum;
    
        // 创建临时文件
        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 校验和。
         *
         * 如果校验和功能已关闭,那么 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;
    
        /* 
         * 使用 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;
    }
    



     将键值对的键、值、过期时间和类型写入到 RDB 中

    /* 
     * 将键值对的键、值、过期时间和类型写入到 RDB 中。
     *
     * 出错返回 -1 。
     *
     * On success if the key was actually saved 1 is returned, otherwise 0
     * is returned (the key was already expired). 
     *
     * 成功保存返回 1 ,当键已经过期时,返回 0 。
     */
    int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
                            long long expiretime, long long now)
    {
        /* Save the expire time 
         *
         * 保存键的过期时间
         */
        if (expiretime != -1) {
            /* If this key is already expired skip it 
             *
             * 不写入已经过期的键
             */
            if (expiretime < now) return 0;
    
            if (rdbSaveType(rdb,REDIS_RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
            if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
        }
    
        /* Save type, key, value 
         *
         * 保存类型,键,值
         */
        if (rdbSaveObjectType(rdb,val) == -1) return -1;
        if (rdbSaveStringObject(rdb,key) == -1) return -1;
        if (rdbSaveObject(rdb,val) == -1) return -1;
    
        return 1;
    }
    



     将键值对的值类型写入到 rdb 中

    /* 
     * 将对象 o 的类型写入到 rdb 中
     */
    int rdbSaveObjectType(rio *rdb, robj *o) {
    
        switch (o->type) {
    
        case REDIS_STRING:
            return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
    
        case REDIS_LIST:
            if (o->encoding == REDIS_ENCODING_ZIPLIST)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
            else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
            else
                redisPanic("Unknown list encoding");
    
        case REDIS_SET:
            if (o->encoding == REDIS_ENCODING_INTSET)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_SET_INTSET);
            else if (o->encoding == REDIS_ENCODING_HT)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_SET);
            else
                redisPanic("Unknown set encoding");
    
        case REDIS_ZSET:
            if (o->encoding == REDIS_ENCODING_ZIPLIST)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET_ZIPLIST);
            else if (o->encoding == REDIS_ENCODING_SKIPLIST)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_ZSET);
            else
                redisPanic("Unknown sorted set encoding");
    
        case REDIS_HASH:
            if (o->encoding == REDIS_ENCODING_ZIPLIST)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH_ZIPLIST);
            else if (o->encoding == REDIS_ENCODING_HT)
                return rdbSaveType(rdb,REDIS_RDB_TYPE_HASH);
            else
                redisPanic("Unknown hash encoding");
    
        default:
            redisPanic("Unknown object type");
        }
    
        return -1; /* avoid warning */
    }
    



     将给定的字符串对象 obj 保存到 rdb 中,我们的key就是通过这个方法保存的

    /*
     * 将给定的字符串对象 obj 保存到 rdb 中。
     *
     * 函数返回 rdb 保存字符串对象所需的字节数。
     *
     * p.s. 代码原本的注释 rdbSaveStringObjectRaw() 函数已经不存在了。
     */
    int rdbSaveStringObject(rio *rdb, robj *obj) {
    
        /* Avoid to decode the object, then encode it again, if the
         * object is already integer encoded. */
        // 尝试对 INT 编码的字符串进行特殊编码
        if (obj->encoding == REDIS_ENCODING_INT) {
            return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
    
        // 保存 STRING 编码的字符串
        } else {
            redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
            return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
        }
    }
    



     将给定对象 o 保存到 rdb 中。

    /* 
     * 将给定对象 o 保存到 rdb 中。
     *
     * 保存成功返回 rdb 保存该对象所需的字节数 ,失败返回 0 。
     *
     * p.s.上面原文注释所说的返回值是不正确的
     */
    int rdbSaveObject(rio *rdb, robj *o) {
        int n, nwritten = 0;
    
        // 保存字符串对象
        if (o->type == REDIS_STRING) {
            /* Save a string value */
            if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
            nwritten += n;
    
        // 保存列表对象
        } else if (o->type == REDIS_LIST) {
            /* Save a list value */
            if (o->encoding == REDIS_ENCODING_ZIPLIST) {
                size_t l = ziplistBlobLen((unsigned char*)o->ptr);
    
                // 以字符串对象的形式保存整个 ZIPLIST 列表
                if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
                nwritten += n;
            } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
                list *list = o->ptr;
                listIter li;
                listNode *ln;
    
                if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
                nwritten += n;
    
                // 遍历所有列表项
                listRewind(list,&li);
                while((ln = listNext(&li))) {
                    robj *eleobj = listNodeValue(ln);
                    // 以字符串对象的形式保存列表项
                    if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                    nwritten += n;
                }
            } else {
                redisPanic("Unknown list encoding");
            }
    
        // 保存集合对象
        } else if (o->type == REDIS_SET) {
            /* Save a set value */
            if (o->encoding == REDIS_ENCODING_HT) {
                dict *set = o->ptr;
                dictIterator *di = dictGetIterator(set);
                dictEntry *de;
    
                if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
                nwritten += n;
    
                // 遍历集合成员
                while((de = dictNext(di)) != NULL) {
                    robj *eleobj = dictGetKey(de);
                    // 以字符串对象的方式保存成员
                    if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                    nwritten += n;
                }
                dictReleaseIterator(di);
            } else if (o->encoding == REDIS_ENCODING_INTSET) {
                size_t l = intsetBlobLen((intset*)o->ptr);
    
                // 以字符串对象的方式保存整个 INTSET 集合
                if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
                nwritten += n;
            } else {
                redisPanic("Unknown set encoding");
            }
    
        // 保存有序集对象
        } else if (o->type == REDIS_ZSET) {
            /* Save a sorted set value */
            if (o->encoding == REDIS_ENCODING_ZIPLIST) {
                size_t l = ziplistBlobLen((unsigned char*)o->ptr);
    
                // 以字符串对象的形式保存整个 ZIPLIST 有序集
                if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
                nwritten += n;
            } else if (o->encoding == REDIS_ENCODING_SKIPLIST) {
                zset *zs = o->ptr;
                dictIterator *di = dictGetIterator(zs->dict);
                dictEntry *de;
    
                if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
                nwritten += n;
    
                // 遍历有序集
                while((de = dictNext(di)) != NULL) {
                    robj *eleobj = dictGetKey(de);
                    double *score = dictGetVal(de);
    
                    // 以字符串对象的形式保存集合成员
                    if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
                    nwritten += n;
    
                    // 成员分值(一个双精度浮点数)会被转换成字符串
                    // 然后保存到 rdb 中
                    if ((n = rdbSaveDoubleValue(rdb,*score)) == -1) return -1;
                    nwritten += n;
                }
                dictReleaseIterator(di);
            } else {
                redisPanic("Unknown sorted set encoding");
            }
    
        // 保存哈希表
        } else if (o->type == REDIS_HASH) {
    
            /* Save a hash value */
            if (o->encoding == REDIS_ENCODING_ZIPLIST) {
                size_t l = ziplistBlobLen((unsigned char*)o->ptr);
    
                // 以字符串对象的形式保存整个 ZIPLIST 哈希表
                if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
                nwritten += n;
    
            } else if (o->encoding == REDIS_ENCODING_HT) {
                dictIterator *di = dictGetIterator(o->ptr);
                dictEntry *de;
    
                if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
                nwritten += n;
    
                // 迭代字典
                while((de = dictNext(di)) != NULL) {
                    robj *key = dictGetKey(de);
                    robj *val = dictGetVal(de);
    
                    // 键和值都以字符串对象的形式来保存
                    if ((n = rdbSaveStringObject(rdb,key)) == -1) return -1;
                    nwritten += n;
                    if ((n = rdbSaveStringObject(rdb,val)) == -1) return -1;
                    nwritten += n;
                }
                dictReleaseIterator(di);
    
            } else {
                redisPanic("Unknown hash encoding");
            }
    
        } else {
            redisPanic("Unknown object type");
        }
    
        return nwritten;
    }
    

    相关文章

      网友评论

        本文标题:redis rdb持久化

        本文链接:https://www.haomeiwen.com/subject/nugisftx.html