RedisDB 序列化

持久化

rdb是redis的一種持久化的方案徒探,他每隔一段時(shí)間將redisdb里面的數(shù)據(jù)序列化到硬盤中保存苞氮。所以喃序列化就是關(guān)鍵。
在Redisdb里面保存的都是key value數(shù)據(jù) . 序列化的話我們其實(shí)就是key value 對(duì)于key來說保存的是string的對(duì)象,可以直接存儲(chǔ)荔仁,對(duì)于value來說,是一個(gè)redisobjet 這個(gè)就是有很多的類型了,可以是string可以是hash乏梁,可以是set次洼。所以先來看下對(duì)于Redisobject的序列化。

RedisObject的序列化

我們都知道我們的RedisObject里面可以存儲(chǔ)很多不同類型的變量遇骑。所以我們序列化有一點(diǎn)是必須的卖毁,那就是我們要保存類型。沒有類型靠?jī)?nèi)存二進(jìn)制反向出來內(nèi)容落萎,操作性基本不大現(xiàn)實(shí)亥啦,所以先來看下類型的定義

#define RDB_TYPE_STRING 0
#define RDB_TYPE_LIST   1
#define RDB_TYPE_SET    2
#define RDB_TYPE_ZSET   3
#define RDB_TYPE_HASH   4
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without */
#define RDB_TYPE_HASH_ZIPMAP    9
#define RDB_TYPE_LIST_ZIPLIST  10
#define RDB_TYPE_SET_INTSET    11
#define RDB_TYPE_ZSET_ZIPLIST  12
#define RDB_TYPE_HASH_ZIPLIST  13
#define RDB_TYPE_LIST_QUICKLIST 14
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 14))
#define RDB_ENC_INT8 0        /* 8 bit signed integer */
#define RDB_ENC_INT16 1       /* 16 bit signed integer */
#define RDB_ENC_INT32 2       /* 32 bit signed integer */
#define RDB_ENC_LZF 3         /* string compressed with FASTLZ */

這個(gè)定義賊簡(jiǎn)潔明了就不多說了看名字就看出來了
再來看下對(duì)類型的保存

int rdbSaveObjectType(rio *rdb, robj *o) {
    switch (o->type) {//先獲取主類型
    case OBJ_STRING://string
        return rdbSaveType(rdb,RDB_TYPE_STRING);//保存成string
    case OBJ_LIST:
        if (o->encoding == OBJ_ENCODING_QUICKLIST)//quicklist
            return rdbSaveType(rdb,RDB_TYPE_LIST_QUICKLIST);
        else
            serverPanic("Unknown list encoding");
    case OBJ_SET://set 有兩種保存方式 intset 和 hashtable
        if (o->encoding == OBJ_ENCODING_INTSET)//
            return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_SET);
        else
            serverPanic("Unknown set encoding");
    case OBJ_ZSET://zet 也有兩種形式 ziplist 和 skiplist
        if (o->encoding == OBJ_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_ZSET_ZIPLIST);
        else if (o->encoding == OBJ_ENCODING_SKIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_ZSET_2);
        else
            serverPanic("Unknown sorted set encoding");
    case OBJ_HASH://hash table 也是兩種  ziplist hashtable
        if (o->encoding == OBJ_ENCODING_ZIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_HASH_ZIPLIST);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_HASH);
        else
            serverPanic("Unknown hash encoding");
    case OBJ_MODULE:
        return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
    default:
        serverPanic("Unknown object type");
    }
    return -1; /* avoid warning */
}

保存完類型之后我們需要的是保存的是我們的value。 我們的value有很多很多類型模暗。對(duì)于我們的value來說不管是string hashtable list這些都有一個(gè)長(zhǎng)度在里面禁悠,我們需要知道我們保持的內(nèi)容有多少。
這個(gè)長(zhǎng)度對(duì)于不同的個(gè)體來說代表的意義不一樣兑宇。比如對(duì)于string肯定是字符才長(zhǎng)度碍侦,對(duì)于hashtable肯定是鍵值對(duì)的個(gè)數(shù)。
現(xiàn)在來看對(duì)len的編碼隶糕,其實(shí)我們對(duì)于編碼還是見過很多了ziplist那些其實(shí)都是大同小異的過一下就好

#define RDB_6BITLEN 0  //六位保存
#define RDB_14BITLEN 1  //十四位保存
#define RDB_32BITLEN 0x80 //4字節(jié)保存
#define RDB_64BITLEN 0x81 //8字節(jié)保存
#define RDB_ENCVAL 3     //保存的是數(shù)字
#define RDB_LENERR UINT64_MAX
//保存長(zhǎng)度
int rdbSaveLen(rio *rdb, uint64_t len) {
    unsigned char buf[2];
    size_t nwritten;

    if (len < (1<<6)) {
        /* Save a 6 bit len */
        buf[0] = (len&0xFF)|(RDB_6BITLEN<<6); //高兩位為00
        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
        nwritten = 1;
    } else if (len < (1<<14)) {
        /* Save a 14 bit len */
        buf[0] = ((len>>8)&0xFF)|(RDB_14BITLEN<<6);//高兩位為01
        buf[1] = len&0xFF;
        if (rdbWriteRaw(rdb,buf,2) == -1) return -1;
        nwritten = 2;
    } else if (len <= UINT32_MAX) {
        /* Save a 32 bit len */
        buf[0] = RDB_32BITLEN;//高兩位為10
        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
        uint32_t len32 = htonl(len);
        if (rdbWriteRaw(rdb,&len32,4) == -1) return -1;
        nwritten = 1+4;
    } else {
        /* Save a 64 bit len */
        buf[0] = RDB_64BITLEN;//高兩位為10 最后一位為1
        if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
        len = htonu64(len);
        if (rdbWriteRaw(rdb,&len,8) == -1) return -1;
        nwritten = 1+8;
    }
    return nwritten;
}
//保存數(shù)字
int rdbEncodeInteger(long long value, unsigned char *enc) { //高兩位做為int的標(biāo)致位
    if (value >= -(1<<7) && value <= (1<<7)-1) {//一位可以保存
        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT8; // 高兩位為11
        enc[1] = value&0xFF;//
        return 2;
    } else if (value >= -(1<<15) && value <= (1<<15)-1) {//兩位可以保存
        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT16;
        enc[1] = value&0xFF;
        enc[2] = (value>>8)&0xFF;
        return 3;
    } else if (value >= -((long long)1<<31) && value <= ((long long)1<<31)-1) {//四位保存
        enc[0] = (RDB_ENCVAL<<6)|RDB_ENC_INT32;
        enc[1] = value&0xFF;
        enc[2] = (value>>8)&0xFF;
        enc[3] = (value>>16)&0xFF;
        enc[4] = (value>>24)&0xFF;
        return 5;
    } else {
        return 0;
    }
}

對(duì)于長(zhǎng)度來說這是一個(gè)數(shù)字瓷产,在后面我們會(huì)發(fā)現(xiàn)在保存字符串的時(shí)候,如果我們的字符串可以轉(zhuǎn)換成一個(gè)數(shù)字的話會(huì)保存成一個(gè)數(shù)字枚驻。因?yàn)檫@樣子可以節(jié)約空間
保存是數(shù)字還是長(zhǎng)度的區(qū)別在于第一個(gè)字符的高兩位
高兩位為11 這時(shí)候就是代表是數(shù)字
來看下具體的獲取數(shù)字

//獲取保存數(shù)字的長(zhǎng)度和保存的是一個(gè)單純的數(shù)字還是一個(gè)長(zhǎng)度
//isencoded 返回代表是一個(gè)數(shù)字還是長(zhǎng)度
//lenptr 在isencoded為0會(huì)讀取具的長(zhǎng)度當(dāng)為1的時(shí)候沒有讀取具體的值只是讀取值保存的長(zhǎng)度
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr) {
    unsigned char buf[2];
    int type;

    if (isencoded) *isencoded = 0;
    if (rioRead(rdb,buf,1) == 0) return -1;
    type = (buf[0]&0xC0)>>6; //獲取高兩位
    if (type == RDB_ENCVAL) {
        /* Read a 6 bit encoding type. */
        if (isencoded) *isencoded = 1;//代表是返回的類型
        *lenptr = buf[0]&0x3F; //獲取低六位  低六位保存的是類型
    } else if (type == RDB_6BITLEN) {//高兩位為0  代表后六位為長(zhǎng)度
        /* Read a 6 bit len. */
        *lenptr = buf[0]&0x3F;//拉取長(zhǎng)度
    } else if (type == RDB_14BITLEN) {//高兩位為01 代表后十四位位長(zhǎng)度
        /* Read a 14 bit len. */
        if (rioRead(rdb,buf+1,1) == 0) return -1;
        *lenptr = ((buf[0]&0x3F)<<8)|buf[1];
    } else if (buf[0] == RDB_32BITLEN) {// 判斷是32位的類型
        /* Read a 32 bit len. */
        uint32_t len;
        if (rioRead(rdb,&len,4) == 0) return -1;//讀取四位
        *lenptr = ntohl(len);//獲取長(zhǎng)度
    } else if (buf[0] == RDB_64BITLEN) {//64位類型
        /* Read a 64 bit len. */
        uint64_t len;
        if (rioRead(rdb,&len,8) == 0) return -1;//讀取八位
        *lenptr = ntohu64(len);
    } else {//錯(cuò)誤的類型
        rdbExitReportCorruptRDB(
            "Unknown length encoding %d in rdbLoadLen()",type);
        return -1; /* Never reached. */
    }
    return 0;
}

在長(zhǎng)度的獲取中我們首先進(jìn)行的是高兩位的校驗(yàn)
當(dāng)發(fā)現(xiàn)高兩位是RDB_ENCVAL他會(huì)設(shè)置標(biāo)識(shí)這是一個(gè)數(shù)字濒旦,并且lenptr也僅僅是保存了數(shù)字保存的長(zhǎng)度并沒有獲取具體的值
當(dāng)高兩位非RDB_ENCVAL會(huì)獲取出具體的len的值當(dāng)返回值為-1代表失敗0代表成功。

Save Object

在Redis保存對(duì)象的過程中他保存類型和保存值是分開的再登。他是先調(diào)用saveobjecttype先把類型保存起來之后尔邓,再調(diào)用saveobject來保存值。所以我們后面來直接看值的保存锉矢。

SaveString

對(duì)于string來說梯嗽,保存存在三種情況。
1.這個(gè)string是否是一個(gè)數(shù)字或者是否能夠被轉(zhuǎn)換成一個(gè)數(shù)字來進(jìn)行保存沽损。
2這個(gè)string是否長(zhǎng)灯节,是否打開了壓縮標(biāo)記。如果是就進(jìn)行壓縮保存绵估。
3這個(gè)string進(jìn)行一個(gè)普通的字符串保存炎疆。

第一種是數(shù)字進(jìn)行保存

ssize_t rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
    unsigned char buf[32];
    ssize_t n, nwritten = 0;
    int enclen = rdbEncodeInteger(value,buf);//對(duì)數(shù)字進(jìn)行編碼
    if (enclen > 0) {//編碼成功
        return rdbWriteRaw(rdb,buf,enclen);//直接寫入buf里面的內(nèi)容
    } else {
        /* Encode as string */
        enclen = ll2string((char*)buf,32,value);//轉(zhuǎn)換才string
        serverAssert(enclen < 32);
        if ((n = rdbSaveLen(rdb,enclen)) == -1) return -1;//先寫入len  返回花費(fèi)的長(zhǎng)度
        nwritten += n;
        if ((n = rdbWriteRaw(rdb,buf,enclen)) == -1) return -1;//寫入內(nèi)容
        nwritten += n;
    }
    return nwritten;//返回寫入的長(zhǎng)度
}

第二中壓縮保存


ssize_t rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
    size_t comprlen, outlen;
    void *out;

    /* We require at least four bytes compression for this to be worth it */
    if (len <= 4) return 0;
    outlen = len-4;
    if ((out = zmalloc(outlen+1)) == NULL) return 0;//分配壓縮后的空間
    comprlen = lzf_compress(s, len, out, outlen);//壓縮
    if (comprlen == 0) {//壓縮失敗
        zfree(out);
        return 0;
    }
    ssize_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);//寫入壓縮后的文件
    zfree(out);
    return nwritten;
}
//保存壓縮對(duì)象
//標(biāo)記位為RDB_ENCVAL<<6)|RDB_ENC_LZF;
//然后保存壓縮后長(zhǎng)度 然后寫入原來的長(zhǎng)度
//最后寫入壓縮后的二進(jìn)制 長(zhǎng)度為壓縮后的長(zhǎng)度
ssize_t rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,
                       size_t original_len) {
    unsigned char byte;
    ssize_t n, nwritten = 0;

    /* Data compressed! Let's save it on disk */
    byte = (RDB_ENCVAL<<6)|RDB_ENC_LZF; //保存格式 壓縮標(biāo)記高兩位也是用的11 低兩位使用的11  在loadlen 代碼可以參考
    if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;//首先寫入壓縮標(biāo)記
    nwritten += n;

    if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;//寫入壓縮后的長(zhǎng)度
    nwritten += n;

    if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;//寫入原來的長(zhǎng)度
    nwritten += n;

    if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;//寫入壓縮后的二進(jìn)制
    nwritten += n;

    return nwritten;

writeerr:
    return -1;
}

第三種保存普通的string對(duì)象

//沒有特別的操作 寫入長(zhǎng)度寫入二進(jìn)制
    /* Store verbatim */
    if ((n = rdbSaveLen(rdb,len)) == -1) return -1;//保存長(zhǎng)度
    nwritten += n;
    if (len > 0) {
        if (rdbWriteRaw(rdb,s,len) == -1) return -1;//寫入二進(jìn)制
        nwritten += len;
    }

最后我們來看下string整體的保存和判斷保存類型流程

//首先保存入口
ssize_t rdbSaveStringObject(rio *rdb, robj *obj) {
    /* Avoid to decode the object, then encode it again, if the
     * object is already integer encoded. */
    //首先判斷是不是一個(gè)數(shù)字
    if (obj->encoding == OBJ_ENCODING_INT) {//這是一個(gè)int的值
        return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
    } else {
        serverAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
        //寫入rawstring
        return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));//寫入
    }
}
//保存數(shù)字的上面已經(jīng)有了 現(xiàn)在看下raw

/* Save a string object as [len][data] on disk. If the object is a string
 * representation of an integer value we try to save it in a special form */
//保存rawstring
//在raw主要進(jìn)行了兩次判斷
//第一次判斷這個(gè)字符串能否轉(zhuǎn)換成一個(gè)數(shù)字進(jìn)行保存
//第二次判斷是判斷是否能夠壓縮然后進(jìn)行壓縮保存
//都不行的話才進(jìn)行普通的string保存
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
    int enclen;
    ssize_t n, nwritten = 0;

    /* Try integer encoding */
    if (len <= 11) {//首先這個(gè)還是蠻小的
        unsigned char buf[5];
        if ((enclen = rdbTryIntegerEncoding((char*)s,len,buf)) > 0) {//嘗試轉(zhuǎn)換成int
            if (rdbWriteRaw(rdb,buf,enclen) == -1) return -1;//如果是int  buf已經(jīng)被編碼了 寫入就行了
            return enclen;
        }
    }

    /* Try LZF compression - under 20 bytes it's unable to compress even
     * aaaaaaaaaaaaaaaaaa so skip it */
    if (server.rdb_compression && len > 20) {//如果配置了壓縮選項(xiàng)
        n = rdbSaveLzfStringObject(rdb,s,len);
        if (n == -1) return -1;
        if (n > 0) return n;
        /* Return value of 0 means data can't be compressed, save the old way */
    }

    /* Store verbatim */
    if ((n = rdbSaveLen(rdb,len)) == -1) return -1;//沒有壓縮標(biāo)記或長(zhǎng)度蠻短的 先寫入長(zhǎng)度
    nwritten += n;
    if (len > 0) {
        if (rdbWriteRaw(rdb,s,len) == -1) return -1;//寫入二進(jìn)制
        nwritten += len;
    }
    return nwritten;
}
其他Object的保存
ssize_t rdbSaveObject(rio *rdb, robj *o) { //save object
    ssize_t n = 0, nwritten = 0;

    if (o->type == OBJ_STRING) {//string
        /* Save a string value */
        if ((n = rdbSaveStringObject(rdb,o)) == -1) return -1;
        nwritten += n;
    } else if (o->type == OBJ_LIST) {//這個(gè)逗比是個(gè)list
        /* Save a list value */
        //對(duì)于我們的quicklist來說 他的每一個(gè)節(jié)點(diǎn)是一個(gè)quicklistnode 他的數(shù)據(jù)區(qū)域是使用的ziplist
        //ziplist是一個(gè)連續(xù)的內(nèi)存塊 所以在為壓縮的node節(jié)點(diǎn)將將ziplist當(dāng)成一個(gè)string來保存
        //如果是一個(gè)壓縮的節(jié)點(diǎn),使用blob的方式的保存
        
        if (o->encoding == OBJ_ENCODING_QUICKLIST) {
            quicklist *ql = o->ptr;
            quicklistNode *node = ql->head;

            if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;//先寫入長(zhǎng)度
            nwritten += n;

            while(node) {
                if (quicklistNodeIsCompressed(node)) {
                    void *data;
                    size_t compress_len = quicklistGetLzf(node, &data);//獲得壓縮后的長(zhǎng)度和壓縮的data
                    //使用壓縮字符串的保存方式
                    if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;//寫入標(biāo)記為壓縮信息
                    nwritten += n;
                } else {
                    //將ziplist當(dāng)成是一個(gè)string直接保存內(nèi)存塊
                    if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;//直接寫入
                    nwritten += n;
                }
                node = node->next;
            }
        } else {
            serverPanic("Unknown list encoding");
        }
    } else if (o->type == OBJ_SET) {//寫入set
        //對(duì)于set 來說 如果我們使用的是hashtable 那么我們只需要遍歷一遍set 把他的key全部寫入就好了
        //如果是intset 這個(gè)玩意就簡(jiǎn)單了 因?yàn)樗且粋€(gè)連續(xù)的二進(jìn)制 當(dāng)成一個(gè)string寫入
        /* Save a set value */
        if (o->encoding == OBJ_ENCODING_HT) {//這玩意是hash
            dict *set = o->ptr;//獲取dict
            dictIterator *di = dictGetIterator(set);//獲取iter
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;//保存長(zhǎng)度
            nwritten += n;

            while((de = dictNext(di)) != NULL) {//遍歷
                sds ele = dictGetKey(de);//拉取key set只有key 具體可看set的保存方式
                if ((n = rdbSaveRawString(rdb,(unsigned char*)ele,sdslen(ele)))//
                    == -1) return -1;
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else if (o->encoding == OBJ_ENCODING_INTSET) {
            size_t l = intsetBlobLen((intset*)o->ptr);//這個(gè)就很6了 直接把這個(gè)玩意當(dāng)成一個(gè)二進(jìn)制錘進(jìn)去
            //當(dāng)成string 進(jìn)行存儲(chǔ)
            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
            nwritten += n;
        } else {
            serverPanic("Unknown set encoding");
        }
    } else if (o->type == OBJ_ZSET) {
        /* Save a sorted set value */
        //第一種情況是一個(gè)ziplst 連續(xù)的內(nèi)存 直接把ziplist當(dāng)成一個(gè)string寫入
        //第二章情況
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
            
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);//連續(xù)的內(nèi)存 編碼什么的 不存在的直接錘擊去

            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;//錘進(jìn)去
            nwritten += n;
        } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
            zset *zs = o->ptr;
            zskiplist *zsl = zs->zsl;

            if ((n = rdbSaveLen(rdb,zsl->length)) == -1) return -1;//先寫入長(zhǎng)度
            nwritten += n;

            /* We save the skiplist elements from the greatest to the smallest
             * (that's trivial since the elements are already ordered in the
             * skiplist): this improves the load process, since the next loaded
             * element will always be the smaller, so adding to the skiplist
             * will always immediately stop at the head, making the insertion
             * O(1) instead of O(log(N)). */
            //這里將只會(huì)保存key和source信息 其他信息將會(huì)被拋棄
            //其他信息可以在讀取的時(shí)候 重新購(gòu)照
            zskiplistNode *zn = zsl->tail;
            while (zn != NULL) {
                if ((n = rdbSaveRawString(rdb,
                      (unsigned char*)zn->ele,sdslen(zn->ele))) == -1)//首先寫入key的信息
                {
                    return -1;
                }
                nwritten += n;
                if ((n = rdbSaveBinaryDoubleValue(rdb,zn->score)) == -1) //再寫入一個(gè)double信息 這里沒有進(jìn)行編碼 因?yàn)閗ey的長(zhǎng)度知道 在我們讀取的時(shí)候 將會(huì)是一個(gè)固定的讀取方式 先讀取出string之后 緊接著會(huì)讀取到一個(gè)double 如果不滿足說明是文件有問題
                    return -1;
                nwritten += n;
                zn = zn->backward;
            }
        } else {
            serverPanic("Unknown sorted set encoding");
        }
    } else if (o->type == OBJ_HASH) {
        /* Save a hash value */
        //對(duì)于hash來說  ziplist 就直接當(dāng)成string寫入
        //hashtable 獲取key value 挨著當(dāng)成string寫入
        if (o->encoding == OBJ_ENCODING_ZIPLIST) { //ziplist
            size_t l = ziplistBlobLen((unsigned char*)o->ptr);//連續(xù)內(nèi)容

            if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;//直接錘進(jìn)去
            nwritten += n;

        } else if (o->encoding == OBJ_ENCODING_HT) {
            dictIterator *di = dictGetIterator(o->ptr);
            dictEntry *de;

            if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;//寫入長(zhǎng)度
            nwritten += n;

            while((de = dictNext(di)) != NULL) {
                sds field = dictGetKey(de);//獲取key
                sds value = dictGetVal(de);//獲取value
                
                if ((n = rdbSaveRawString(rdb,(unsigned char*)field,
                        sdslen(field))) == -1) return -1;//錘入key
                nwritten += n;
                if ((n = rdbSaveRawString(rdb,(unsigned char*)value,
                        sdslen(value))) == -1) return -1; //錘入value
                nwritten += n;
            }
            dictReleaseIterator(di);
        } else {
            serverPanic("Unknown hash encoding");
        }

    } else if (o->type == OBJ_MODULE) {
        /* Save a module-specific value. */
        RedisModuleIO io;
        moduleValue *mv = o->ptr;
        moduleType *mt = mv->type;
        moduleInitIOContext(io,mt,rdb);

        /* Write the "module" identifier as prefix, so that we'll be able
         * to call the right module during loading. */
        int retval = rdbSaveLen(rdb,mt->id);
        if (retval == -1) return -1;
        io.bytes += retval;

        /* Then write the module-specific representation + EOF marker. */
        mt->rdb_save(&io,mv->value);
        retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
        if (retval == -1) return -1;
        io.bytes += retval;

        if (io.ctx) {
            moduleFreeContext(io.ctx);
            zfree(io.ctx);
        }
        return io.error ? -1 : (ssize_t)io.bytes;
    } else {
        serverPanic("Unknown object type");
    }
    return nwritten;
}

對(duì)對(duì)象的存儲(chǔ)可以總結(jié)下
首先存儲(chǔ)的是對(duì)象的類型国裳,對(duì)應(yīng)的是SaveObjetType方法這個(gè)方法其實(shí)就是把類型編號(hào)存儲(chǔ)文件形入。
然后存儲(chǔ)Object的大小對(duì)于大小這個(gè)類型來說類型不同是不一樣的。對(duì)于string來說將會(huì)存儲(chǔ)的是一個(gè)string的長(zhǎng)度缝左,但是對(duì)于list來說就會(huì)是這個(gè)list的元素格式亿遂。具體會(huì)以類型和編碼為主螟蒸。
在存儲(chǔ)過程中遵循如果是連續(xù)的內(nèi)存塊就使用string的存儲(chǔ)方式。這樣如果配置了壓縮可以使用到壓縮節(jié)約內(nèi)存崩掘。而且當(dāng)讀取的時(shí)候也可以根據(jù)C語(yǔ)言的內(nèi)存模型直接反向出原類型七嫌。
當(dāng)不是連續(xù)內(nèi)存塊的時(shí)候就度取出關(guān)鍵類型。當(dāng)做string來存儲(chǔ)(除了zset的那個(gè)source)苞慢。

文件存儲(chǔ)redisDb

現(xiàn)在我們知道了對(duì)于對(duì)象來說Redis的存儲(chǔ)方式诵原,對(duì)于我們的redisDb來說他就是一系列的key value,key是一個(gè)string value是個(gè)object所以存儲(chǔ)來說就會(huì)很簡(jiǎn)單
在存儲(chǔ)具體的值之前我們的db還需要一列額外的記錄挽放,比如當(dāng)前是哪個(gè)db啊绍赛。我們的這個(gè)key是不是有超時(shí)信息。所以可以看下這部分是怎么實(shí)現(xiàn)的

/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
//對(duì)于這些值來將會(huì)被當(dāng)做type來存儲(chǔ) 在load的時(shí)候讀取到這些類型將會(huì)被特殊處理
#define RDB_OPCODE_AUX        250 //設(shè)備信息 代表后面是兩個(gè)string作為鍵值對(duì)保存的設(shè)備的一個(gè)信息
#define RDB_OPCODE_RESIZEDB   251 //db的大小信息 代表后面會(huì)保存兩個(gè)長(zhǎng)度信息 一個(gè)保存數(shù)據(jù)的dict的大小 一個(gè)保存過期鍵的信息
#define RDB_OPCODE_EXPIRETIME_MS 252 //過期信息毫秒 代表后面將會(huì)讀取一個(gè)int64大小的數(shù)字
#define RDB_OPCODE_EXPIRETIME 253 //過期時(shí)間秒 后面會(huì)跟一個(gè)int32大小的時(shí)間
#define RDB_OPCODE_SELECTDB   254  //選擇數(shù)據(jù)庫(kù)
#define RDB_OPCODE_EOF        255 //數(shù)據(jù)庫(kù)保存結(jié)束標(biāo)記


/* Save an AUX field. */
//保存設(shè)備的一些信息 后面跟兩個(gè)string load的時(shí)候之間讀取
ssize_t rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
    ssize_t ret, len = 0;
    if ((ret = rdbSaveType(rdb,RDB_OPCODE_AUX)) == -1) return -1;//標(biāo)記為設(shè)備信息
    len += ret;
    if ((ret = rdbSaveRawString(rdb,key,keylen)) == -1) return -1;//寫入key
    len += ret;
    if ((ret = rdbSaveRawString(rdb,val,vallen)) == -1) return -1;//寫入value
    len += ret;
    return len;
}
//保存db信息 后面之間跟兩個(gè)len信息
     uint32_t db_size, expires_size;
        db_size = (dictSize(db->dict) <= UINT32_MAX) ?
                                dictSize(db->dict) :
                                UINT32_MAX;
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
                                dictSize(db->expires) :
                                UINT32_MAX;
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;//記錄數(shù)據(jù)庫(kù)大小信息
        if (rdbSaveLen(rdb,db_size) == -1) goto werr;//dbsize
        if (rdbSaveLen(rdb,expires_size) == -1) goto werr;//expires_size

//select
        /* Write the SELECT DB opcode */
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;//進(jìn)入db的時(shí)候?qū)?huì)使用select
        if (rdbSaveLen(rdb,j) == -1) goto werr;//用len直接來保存db的編號(hào)

//RDB_OPCODE_EXPIRETIME_MS
 if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
         int64_t t64 = (int64_t) t;
    return rdbWriteRaw(rdb,&t64,8);

//RDB_OPCODE_EXPIRETIME 
//沒看到用 不過喃
 if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME) == -1) return -1;
    int32_t t32 = (int32_t) t;
    return rdbWriteRaw(rdb,&t32,4);

//RDB_OPCODE_EOF
rdbSaveType(rdb,RDB_OPCODE_EOF) ;//直接就是寫了個(gè)標(biāo)記

對(duì)于我們的沒有redisDb的保存項(xiàng)存儲(chǔ)的時(shí)候可以提取出三個(gè)東西key,value,expire辑畦。
來看下存儲(chǔ)著三個(gè)東西的流程

/* Save a key-value pair, with expire time, type, key, value.
 * On error -1 is returned.
 * On success if the key was actually saved 1 is returned, otherwise 0
 * is returned (the key was already expired). */
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;//如果已經(jīng)過期了 肯定就不需要存儲(chǔ)了
        if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;//首先存儲(chǔ)expire信息
        if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
    }

    /* Save type, key, value */
    if (rdbSaveObjectType(rdb,val) == -1) return -1;//value type
    if (rdbSaveStringObject(rdb,key) == -1) return -1; //key
    if (rdbSaveObject(rdb,val) == -1) return -1;//value
    return 1;
}

對(duì)于我們的流程來說首先就是判斷exipre是否存在是否過期等吗蚌。然后才是存儲(chǔ)我們的key value信息。
對(duì)于過期時(shí)間就是首先加上一個(gè)RDB_OPCODE_EXPIRETIME_MS后面之間跟上一個(gè)過期時(shí)間
對(duì)于key value 將首先存儲(chǔ)的是value的類型信息 然后將會(huì)保存string類型的key 最后會(huì)調(diào)用我們的object保存方法保存我們的value
對(duì)于存在exipre 就會(huì)是 RDB_OPCODE_EXPIRETIME_MS exipre valuetype key value 這種格式保存的纯出。
下面下整體的保存


/* Produces a dump of the database in RDB format sending it to the specified
 * Redis I/O channel. On success C_OK is returned, otherwise C_ERR
 * is returned and part of the output, or all the output, can be
 * missing because of I/O errors.
 *
 * When the function returns C_ERR and if 'error' is not NULL, the
 * integer pointed by 'error' is set to the value of errno just after the I/O
 * error. */
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
    dictIterator *di = NULL;
    dictEntry *de;
    char magic[10];
    int j;
    long long now = mstime();//當(dāng)前的時(shí)間
    uint64_t cksum;
    size_t processed = 0;

    if (server.rdb_checksum)
        rdb->update_cksum = rioGenericUpdateChecksum; //校驗(yàn)和
    snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);//首先寫入redis版本號(hào)信息 版本使用%04將會(huì)占用4個(gè)字節(jié)
    if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;//redis 5個(gè)字節(jié)加上版本號(hào)四個(gè)字節(jié)為九個(gè)字節(jié)
    if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr; //寫入設(shè)備信息

    for (j = 0; j < server.dbnum; j++) {//循環(huán)遍歷所有的db
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);
        if (!di) return C_ERR;

        /* Write the SELECT DB opcode */
        if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;//進(jìn)入db的時(shí)候?qū)?huì)使用select
        if (rdbSaveLen(rdb,j) == -1) goto werr;

        /* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
         * is currently the largest type we are able to represent in RDB sizes.
         * However this does not limit the actual size of the DB to load since
         * these sizes are just hints to resize the hash tables. */
        uint32_t db_size, expires_size;
        db_size = (dictSize(db->dict) <= UINT32_MAX) ?
                                dictSize(db->dict) :
                                UINT32_MAX;
        expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
                                dictSize(db->expires) :
                                UINT32_MAX;
        if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;//記錄數(shù)據(jù)庫(kù)大小信息
        if (rdbSaveLen(rdb,db_size) == -1) goto werr;//dbsize
        if (rdbSaveLen(rdb,expires_size) == -1) goto werr;//expires_size

        /* Iterate this DB writing every entry */
        while((de = dictNext(di)) != NULL) {
            sds keystr = dictGetKey(de);
            robj key, *o = dictGetVal(de);
            long long expire;

            initStaticStringObject(key,keystr);
            expire = getExpire(db,&key);
            if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;

            /* When this RDB is produced as part of an AOF rewrite, move
             * accumulated diff from parent to child while rewriting in
             * order to have a smaller final write. */
            if (flags & RDB_SAVE_AOF_PREAMBLE &&
                rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
            {
                processed = rdb->processed_bytes;
                aofReadDiffFromParent();
            }
        }
        dictReleaseIterator(di);
    }
    di = NULL; /* So that we don't release it again on error. */

    /* If we are storing the replication information on disk, persist
     * the script cache as well: on successful PSYNC after a restart, we need
     * to be able to process any EVALSHA inside the replication backlog the
     * master will send us. */
    if (rsi && dictSize(server.lua_scripts)) {//lua
        di = dictGetIterator(server.lua_scripts);
        while((de = dictNext(di)) != NULL) {
            robj *body = dictGetVal(de);
            if (rdbSaveAuxField(rdb,"lua",3,body->ptr,sdslen(body->ptr)) == -1)
                goto werr;
        }
        dictReleaseIterator(di);
    }

    /* EOF opcode */
    if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;//保存eof

    /* CRC64 checksum. It will be zero if checksum computation is disabled, the
     * loading code skips the check in this case. */
    cksum = rdb->cksum;
    memrev64ifbe(&cksum);
    if (rioWrite(rdb,&cksum,8) == 0) goto werr;//保存校驗(yàn)和
    return C_OK;

werr:
    if (error) *error = errno;
    if (di) dictReleaseIterator(di);
    return C_ERR;
}

對(duì)于我們的保存的流程
1.保存魔數(shù)和version 將使用raw的方式直接寫入固定長(zhǎng)度
2.保存aux信息 aux信息有很多組都是固定的格式 保存aux標(biāo)記后后面跟上兩個(gè)string信息
3.開始保存一個(gè)db的信息蚯妇。首先使用RDB_OPCODE_SELECTDB來保存db的id,后面是直接放置的一個(gè)len的數(shù)字暂筝。然后使用RDB_OPCODE_RESIZEDB標(biāo)記來保存dbsize 和expires_size 箩言。
3具體保存每一個(gè)key value 信息。如果存在expire信息就是會(huì)RDB_OPCODE_EXPIRETIME_MS int64(time) valuetype key value 這種格式存儲(chǔ)焕襟。沒有expire的話就是直接valuetype key value 保存信息陨收。
4.保存eof信息這個(gè)就是直接保存一個(gè)標(biāo)記
5.最后保存一個(gè)8字節(jié)的校驗(yàn)和

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鸵赖,隨后出現(xiàn)的幾起案子务漩,更是在濱河造成了極大的恐慌,老刑警劉巖它褪,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饵骨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡列赎,警方通過查閱死者的電腦和手機(jī)宏悦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門镐确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來包吝,“玉大人,你說我怎么就攤上這事源葫∈剑” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵息堂,是天一觀的道長(zhǎng)嚷狞。 經(jīng)常有香客問我块促,道長(zhǎng),這世上最難降的妖魔是什么床未? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任竭翠,我火速辦了婚禮,結(jié)果婚禮上薇搁,老公的妹妹穿的比我還像新娘斋扰。我一直安慰自己,他們只是感情好啃洋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布传货。 她就那樣靜靜地躺著,像睡著了一般宏娄。 火紅的嫁衣襯著肌膚如雪问裕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天孵坚,我揣著相機(jī)與錄音粮宛,去河邊找鬼。 笑死卖宠,一個(gè)胖子當(dāng)著我的面吹牛窟勃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逗堵,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼秉氧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蜒秤?” 一聲冷哼從身側(cè)響起汁咏,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎作媚,沒想到半個(gè)月后攘滩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纸泡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年漂问,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片女揭。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚤假,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吧兔,到底是詐尸還是另有隱情磷仰,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布境蔼,位于F島的核電站灶平,受9級(jí)特大地震影響伺通,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逢享,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一罐监、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞒爬,春花似錦笑诅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至俊犯,卻和暖如春妇多,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燕侠。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工者祖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绢彤。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓七问,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親茫舶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子械巡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)饶氏,斷路器讥耗,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 分布式緩存技術(shù)PK:選擇Redis還是Memcached古程? 經(jīng)平臺(tái)同意授權(quán)轉(zhuǎn)載 作者:田京昆(騰訊后臺(tái)研發(fā)工程師)...
    meng_philip123閱讀 68,910評(píng)論 7 60
  • 之前網(wǎng)上搜索到很多的方法,但是會(huì)出問題喊崖,這里分享下挣磨! -(UIImage *)cvsamplebufferrefT...
    冬的天閱讀 3,284評(píng)論 1 0
  • 秋,悄然而來 夏天荤懂,在幾度花開的火熱里 漸漸失去了絢爛的色彩 早起的一縷秋風(fēng) 攜來黃昏涼爽的雨袂 誰(shuí)采西天的晚霞別...
    晨風(fēng)世界閱讀 319評(píng)論 0 0
  • 今晚聽了小芳的分享會(huì)茁裙。 以前一直在微博上關(guān)注她,之前的幾屆分享會(huì)都是在廈門現(xiàn)場(chǎng)開势誊,今年第一次嘗試用音頻的形式做微信...
    Lily5566閱讀 170評(píng)論 0 0