Redis zipmap

簡介

Redis zipmap是一個String -> String Map data structure optimized for size柑爸。不要被它的名字所迷惑,它實際上沒有做壓縮操作炫掐。是一個連續(xù)的key value集合仓蛆。
它的特點就是維護(hù)map需要的額外信息很少。最少可以到達(dá)只需要三個字節(jié)蝌诡。不過這樣做的后果就是時間復(fù)雜度為o(N),需要便利才能操作溉贿。所以相對來說在定義上ZIPMAP_BIGLEN 就只有254(不過并非到達(dá)254就不給你存了,它還是會存的).

數(shù)據(jù)結(jié)構(gòu)

zipmap的數(shù)據(jù)結(jié)構(gòu)特別簡單,簡單到兩個字節(jié)浦旱,首字節(jié)保存長度宇色,尾字節(jié)保存結(jié)尾標(biāo)志ZIPMAP_BIGLEN。ZIPMAP_BIGLEN=254

創(chuàng)建zipmap

unsigned char *zipmapNew(void) {
    unsigned char *zm = zmalloc(2);

    zm[0] = 0; /* Length */
    zm[1] = ZIPMAP_END;
    return zm;
}

對就是這么簡單闽寡。分配兩字節(jié)的空間設(shè)置首字節(jié)為長度0代兵,尾字節(jié)為結(jié)束標(biāo)識。

數(shù)據(jù)編碼

在說插入之前我們先來看一下爷狈,對于一個key->value的數(shù)據(jù),zipmap是怎么保存的裳擎。
引用源碼中的介紹
Memory layout of a zipmap, for the map "foo" => "bar", "hello" => "world":
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"

  1. zmlen 這個是我們的首字節(jié)保存key value的對數(shù)涎永。不過zmlen <=ZIPMAP_BIGLEN大了他就不表示了。
  2. len 表示保存的字符串的長度鹿响。第一個len是key的長度羡微。第二個len是value字符串的長度。len在小于254的時候使用一個字節(jié)表示惶我。這個字節(jié)就是保存len的長度妈倔。當(dāng)len大于254的時候。這時候使用sizeof(unsigned int)+1來保存绸贡。其中第一個字節(jié)設(shè)置為ZIPMAP_BIGLEN盯蝴。后四個字節(jié)用來實際存儲長度毅哗。
  3. free 一個字節(jié),用在修改key對應(yīng)的value后表示剩余的空間捧挺。發(fā)生在新的value比原來的value的len長度要小的時候虑绵。受到ZIPMAP_VALUE_MAX_FREE的限制。當(dāng)大于ZIPMAP_VALUE_MAX_FREE的時候觸發(fā)resize
  4. keyvalue鍵值對直接沒有對于的數(shù)據(jù)闽烙,直接連接

len的編碼

因為我們的len有著兩種保存方式翅睛。所以先來看下len相關(guān)的編碼和解碼

/*獲取保存len需要的長度 當(dāng)len<ZIPMAP_BIGLEN時為1,len>ZIPMAP_BIGLEN時為sizeof(unsigned int)+1)*/
#define ZIPMAP_LEN_BYTES(_l) (((_l) < ZIPMAP_BIGLEN) ? 1 : sizeof(unsigned int)+1)

/*對len進(jìn)行編碼,當(dāng)沒有傳入p時黑竞,返回需要的空間大小捕发,當(dāng)p存在時編碼保存len并且返回空間大小*/
static unsigned int zipmapEncodeLength(unsigned char *p, unsigned int len) {
    if (p == NULL) {
        return ZIPMAP_LEN_BYTES(len);//為空直接返回大小
    } else {
        if (len < ZIPMAP_BIGLEN) {
            p[0] = len;//當(dāng)len<ZIPMAP_BIGLEN時直接保存
            return 1;
        } else {
            p[0] = ZIPMAP_BIGLEN;//首字節(jié)設(shè)置為ZIPMAP_BIGLEN作為標(biāo)記
            memcpy(p+1,&len,sizeof(len));
            memrev32ifbe(p+1);//對于大端小斷的操作
            return 1+sizeof(len);
        }
    }
}

/*解len長度*/
static unsigned int zipmapDecodeLength(unsigned char *p) {
    unsigned int len = *p;

    if (len < ZIPMAP_BIGLEN) return len;//不是ZIPMAP_BIGLEN標(biāo)記直接返回長度
    memcpy(&len,p+1,sizeof(unsigned int));
    memrev32ifbe(&len);//大端小端
    return len;
}

key value編碼

對于key的編碼保存方式的<len>key
對于value的編碼是<len><free>value
對于一個key->value的鍵值對的編碼則是<len>key<len><free>value,他們中間沒有間隔直接連接起來很魂。
我們獲取一個key-value所需要的總長度

static unsigned long zipmapRequiredLength(unsigned int klen, unsigned int vlen) {
    unsigned int l;

    l = klen+vlen+3;// 3 = klen vlen free(一般是klen vlen一般是1)
    if (klen >= ZIPMAP_BIGLEN) l += 4;
    if (vlen >= ZIPMAP_BIGLEN) l += 4;
    return l;
}

key value的解碼

對于key爬骤,先獲得klen 然后獲得保存klen所需要的長度klenlen,再偏移klenlen的長度得到保存的key值

unsigned char * p;//表示保存key的開始
unsigned int klen = zipmapDecodeLength(p);
unsigned int klenlen = zipmapEncodeLength(NULL,klen);
unsigned char* key = p+klenlen;

對于value來說基本和key一致莫换,因為在value實際保存位置前霞玄,還有一個一字節(jié)的free的空間,所以我們需要多偏移一個字節(jié)拉岁。

unsigned char * p;//表示保存value的開始
unsigned int vlen = zipmapDecodeLength(p);
unsigned int vlenlen = zipmapEncodeLength(NULL,vlen);
unsigned char* key = p+vlenlen+1;

搜索

我們知道編碼和保存方式之后坷剧,來看下我們的查找操作

static unsigned char *zipmapLookupRaw(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned int *totlen) {
 /*1*/   unsigned char *p = zm+1, *k = NULL;//zm實際保存的數(shù)據(jù)從第二個字節(jié)開始
    unsigned int l,llen;

    while(*p != ZIPMAP_END) {//沒有結(jié)束
        unsigned char free;

        /* Match or skip the key */
 /*2*/  l = zipmapDecodeLength(p);//長度打頭
        llen = zipmapEncodeLength(NULL,l);//返回length的長度
/*3*/   if (key != NULL && k == NULL && l == klen && !memcmp(p+llen,key,l)) {//k為null是才比較不為null代表找到
            /* Only return when the user doesn't care
             * for the total length of the zipmap. */
            if (totlen != NULL) {//返回長度不
                k = p;
            } else {
                return p;//直接返回
            }
        }
        p += llen+l; //偏移數(shù)據(jù)的長度和保存長度的長度
        /* Skip the value as well */
/*4*/l = zipmapDecodeLength(p);//value的長度
        p += zipmapEncodeLength(NULL,l);//保持value長度的長度
        free = p[0];
        p += l+1+free; /* +1 to skip the free byte */
    }
    if (totlen != NULL) *totlen = (unsigned int)(p-zm)+1;
    return k;
}

對于我們的搜索key來說特別簡單。
1.我們先對傳入的zm進(jìn)行1的偏移喊暖,因為第二個字節(jié)才是保存的第一個key的開始惫企。
2.獲得key的值
3.對key進(jìn)行比對,成功代表找到陵叽,沒成功進(jìn)入偏移掉key的長度狞尔,進(jìn)入到4
4.偏移一個value的長度,value偏移的時候需要注意不要忘記free保存的空閑長度和free本身的一字節(jié)的長度。
最后如果傳入了totlen則代表需要獲取保存所有數(shù)據(jù)的長度

插入

unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int klen, unsigned char *val, unsigned int vlen, int *update) {
    unsigned int zmlen, offset;
    unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);//獲取key value保存需要的總長度
    unsigned int empty, vempty;
    unsigned char *p;

    freelen = reqlen;
    if (update) *update = 0;
/*1*/p = zipmapLookupRaw(zm,key,klen,&zmlen);//查找key是否存在并且返回總長度
    if (p == NULL) {//代表沒有
        /* Key not found: enlarge */
   /*2*/zm = zipmapResize(zm, zmlen+reqlen);//重新分配
        p = zm+zmlen-1;
        zmlen = zmlen+reqlen;

        /* Increase zipmap length (this is an insert) */
        if (zm[0] < ZIPMAP_BIGLEN) zm[0]++;
    } else {
        /* Key found. Is there enough space for the new value? */
        /* Compute the total length: */
        if (update) *update = 1;
        freelen = zipmapRawEntryLength(p);//獲取原來保存key value的總大小
        if (freelen < reqlen) {//總大小不夠
            /* Store the offset of this key within the current zipmap, so
             * it can be resized. Then, move the tail backwards so this
             * pair fits at the current position. */
      /*3*/  offset = p-zm;//原來保存key value的偏移
            zm = zipmapResize(zm, zmlen-freelen+reqlen);//重新分配空間
            p = zm+offset;//偏移到原來保存key value的位置

            /* The +1 in the number of bytes to be moved is caused by the
             * end-of-zipmap byte. Note: the *original* zmlen is used. */
            memmove(p+reqlen, p+freelen,zmlen - offset -freelen -1 );//內(nèi)存移動過來
            /*
             我們需要移動原來的keyvlaue后的所有數(shù)據(jù)(排除最后一個 在resize中已經(jīng)設(shè)置)到新的keyvalue的結(jié)束位置
             p = zm +offset 所以這個時候p是保存這個key value開始的位置
             reqlen新key value保存需要的總大小 所以p+reqlen 就到達(dá)下個key value的起始位置(p+reqlen-1 為新key value的結(jié)束位置)
             freelen 保存原來key value的總大小p+freelen 同里就達(dá)到了原來下一個key value保存的位置
             這個時候我們把zm看成一個純的字符串巩掺,來看內(nèi)存移動
             zmlen為字符串的長度偏序,p+reqlen為需要移動到的起始位置,p+freelen為需要移動的起始位置
             offset為zm到原來字符串中間數(shù)據(jù)的長度 freelen 為原來kevalue的長度
             zmlen - offset -freelen 就等于剩下數(shù)據(jù)的長度 -1是因為最后一個數(shù)據(jù)已經(jīng)設(shè)置不需要移動
             */
            zmlen = zmlen-freelen+reqlen;//獲得新大小
            freelen = reqlen;//這時候沒有剩余空間
        }
    }

    /* We now have a suitable block where the key/value entry can
     * be written. If there is too much free space, move the tail
     * of the zipmap a few bytes to the front and shrink the zipmap,
     * as we want zipmaps to be very space efficient. */
    empty = freelen-reqlen;
    if (empty >= ZIPMAP_VALUE_MAX_FREE) {//如果剩的太多了
        /* First, move the tail <empty> bytes to the front, then resize
         * the zipmap to be <empty> bytes smaller. */
    /*4*/offset = p-zm;
        memmove(p+reqlen, p+freelen, zmlen-(offset+freelen+1));//回收內(nèi)存
        /*
         內(nèi)存前移和后移一樣的 resize會設(shè)置最后一個
         */
        zmlen -= empty;//變小
        zm = zipmapResize(zm, zmlen);
        p = zm+offset;//
        vempty = 0;
    } else {
        vempty = empty;
      
    }

    /* Just write the key + value and we are done. */
    /* Key: */
    /*5*/
    p += zipmapEncodeLength(p,klen);//設(shè)置并返回
    memcpy(p,key,klen);//拷貝key
    p += klen;
    /* Value: */
    p += zipmapEncodeLength(p,vlen);
    *p++ = vempty;//設(shè)置free
    memcpy(p,val,vlen);
    return zm;
}

設(shè)置一個key value經(jīng)歷了以下步驟

  1. 首先對key進(jìn)行搜索操作同時返回zmlen
    2.如果沒有搜索到胖替,那么直接分配一個keyvalue所需要的空間
    3.如果搜索到原來有研儒,那么判斷當(dāng)前需要的空間和原來空間的大小,如果當(dāng)前空間比原來空間小独令,那么就需要再次分配一個大的內(nèi)存空間端朵,然后把p+freelen后的數(shù)據(jù)移動到p+reqlen,不過需要注意的是在代碼中燃箭,最末尾的標(biāo)記是在resize的時候設(shè)置了冲呢,所以在移動的時候少了一個移動
    4.當(dāng)原來的空間比需要的空間大,并且當(dāng)freelen-reqlen的時候大于了允許的最大freesize招狸,那么就需要重新分配一個小的空間敬拓,也是把p+freelen后的數(shù)據(jù)移動到p+reqlen邻薯,不過需要注意的是在代碼中,最末尾的標(biāo)記是在resize的時候設(shè)置了恩尾,所以在移動的時候少了一個移動
    5.最后把新的keyvalue編碼到新的空間弛说。

刪除

unsigned char *zipmapDel(unsigned char *zm, unsigned char *key, unsigned int klen, int *deleted) {
    unsigned int zmlen, freelen;
    unsigned char *p = zipmapLookupRaw(zm,key,klen,&zmlen);//找到
    if (p) {
        freelen = zipmapRawEntryLength(p);//獲取這個keyvalue總長度
        memmove(p, p+freelen, zmlen-((p-zm)+freelen+1));//回收內(nèi)存 也注意最后一位
        zm = zipmapResize(zm, zmlen-freelen);

        /* Decrease zipmap length */
        if (zm[0] < ZIPMAP_BIGLEN) zm[0]--;

        if (deleted) *deleted = 1;
    } else {
        if (deleted) *deleted = 0;
    }
    return zm;
}

刪除相對來說就是特別的簡單了
如果找到key那么或取這個keyvalue的空間,前移keyvalue后面的數(shù)據(jù)翰意,更改大小

總結(jié)

zipmap的數(shù)據(jù)結(jié)構(gòu)相對簡單木人。我們只需要理解了keyvalue的編碼以后,就能快速的掌握冀偶。在插入和刪除的時候牽涉到的內(nèi)存移動醒第,都要注意在源碼中沒有移動最后一位。因為是在resize中設(shè)置了进鸠。

使用的地方

The Redis Hash type uses this data structure for hashes composed of a small number of elements, to switch to a hash table once a given number ofelements is reached.
它使用在當(dāng)hashtable還特別小的時候稠曼。因為hashtable需要編碼的額外信息很大 sizeof(struct dictEntry) = 24,咱們的zipmap最少只需要三個客年。最多11個霞幅。
在查詢效率上當(dāng)數(shù)據(jù)量特別小的時候順序查詢花費的時間成本雖然是o(N),但是N小量瓜,所以是可以接受的司恳。這樣可以節(jié)約出內(nèi)存。
我們以后還會看到很多這種節(jié)約內(nèi)存的做法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍傲,一起剝皮案震驚了整個濱河市扔傅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烫饼,老刑警劉巖猎塞,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異杠纵,居然都是意外死亡荠耽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淡诗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骇塘,“玉大人,你說我怎么就攤上這事韩容。” “怎么了唐瀑?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵群凶,是天一觀的道長。 經(jīng)常有香客問我哄辣,道長请梢,這世上最難降的妖魔是什么赠尾? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮毅弧,結(jié)果婚禮上气嫁,老公的妹妹穿的比我還像新娘。我一直安慰自己够坐,他們只是感情好寸宵,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著元咙,像睡著了一般梯影。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庶香,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天甲棍,我揣著相機(jī)與錄音,去河邊找鬼赶掖。 笑死感猛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奢赂。 我是一名探鬼主播陪白,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼呈驶!你這毒婦竟也來了拷泽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤袖瞻,失蹤者是張志新(化名)和其女友劉穎司致,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聋迎,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡脂矫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霉晕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庭再。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖牺堰,靈堂內(nèi)的尸體忽然破棺而出拄轻,到底是詐尸還是另有隱情,我是刑警寧澤伟葫,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布恨搓,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斧抱。R本人自食惡果不足惜常拓,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辉浦。 院中可真熱鬧弄抬,春花似錦、人聲如沸宪郊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽废膘。三九已至竹海,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丐黄,已是汗流浹背斋配。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留灌闺,地道東北人艰争。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像桂对,于是被迫代替她去往敵國和親甩卓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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

  • Redis的內(nèi)存優(yōu)化 聲明:本文內(nèi)容來自《Redis開發(fā)與運維》一書第八章蕉斜,如轉(zhuǎn)載請聲明逾柿。 Redis所有的數(shù)據(jù)都...
    meng_philip123閱讀 18,874評論 2 29
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)宅此,斷路器机错,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • zipmap 是redis里的hashmap, 主要在保證速度的同時減少內(nèi)存的使用父腕,存儲結(jié)構(gòu)是一個字符串弱匪,通過內(nèi)存...
    lmem閱讀 1,029評論 0 0
  • 放下放不下的 時過境遷 不過都是過眼云煙 看不懂的看不透 事過境遷 隨心所欲好過事與愿違 填不滿的黑洞 含了就化的...
    未來醬紫閱讀 196評論 0 0
  • 東北的深秋,景色蕭條璧亮。凌冽的秋風(fēng)席卷枯黃的落葉萧诫,風(fēng)沙吹打人的臉龐。一座破敗的庭院前枝嘶,一個裹著小腳的女人癡癡望向遠(yuǎn)方...
    6水中颶風(fēng)6閱讀 248評論 8 18