Redis的數(shù)據(jù)結(jié)構(gòu)(三):字典

字典在redis的應(yīng)用

字典在我們平時(shí)的編程中是一種非常常見的數(shù)據(jù)結(jié)構(gòu)筒主,它有著結(jié)構(gòu)簡(jiǎn)單撕捍,查找快速的優(yōu)點(diǎn)迈嘹,而在redis中削彬,字典的應(yīng)用更是十分廣泛。redis本身是一個(gè)key-value型的nosql數(shù)據(jù)庫秀仲,因此數(shù)據(jù)庫本身就是由字典而構(gòu)成的融痛,數(shù)據(jù)庫的curd都是在此基礎(chǔ)上進(jìn)行的。除此之外神僵,redis的map類型也是由字典這個(gè)結(jié)構(gòu)實(shí)現(xiàn)的雁刷。

字典的實(shí)現(xiàn)

c語言不像我們平時(shí)用習(xí)慣的高級(jí)編程語言一樣,它沒有內(nèi)置字典結(jié)構(gòu)挑豌,因此redis自身實(shí)現(xiàn)了一套字典結(jié)構(gòu)安券,下面我們來簡(jiǎn)單分析下實(shí)現(xiàn)字典的幾個(gè)結(jié)構(gòu)體。

1. 哈希表

typedef struct dictht {
    
    // 哈希表數(shù)組
    dictEntry **table;

    // 哈希表大小
    unsigned long size;
    
    // 哈希表大小掩碼氓英,用于計(jì)算索引值
    // 總是等于 size - 1
    unsigned long sizemask;

    // 該哈希表已有節(jié)點(diǎn)的數(shù)量
    unsigned long used;

} dictht;

這里要注意的就是侯勉,table字段是一個(gè)可以保存多個(gè)dictEntry的數(shù)組,也就是一個(gè)哈希表里面會(huì)有多個(gè)哈希的實(shí)體類铝阐,也可以成為哈希節(jié)點(diǎn)址貌。

2. 哈希節(jié)點(diǎn)

typedef struct dictEntry {
    
    // 鍵
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下個(gè)哈希表節(jié)點(diǎn),形成鏈表
    struct dictEntry *next;

} dictEntry;

v字段表示哈希節(jié)點(diǎn)的值徘键,使用union結(jié)構(gòu)體表示這個(gè)值可以使void指針類型练对,也可以是uint64_t或者int64_t整數(shù),這里我猜想是為了其他操作系統(tǒng)而做的一個(gè)兼容吧吹害。盡管大部分哈希函數(shù)具有很強(qiáng)的防碰撞性螟凭,但是也會(huì)遇到哈希值相同的鍵值對(duì),這個(gè)時(shí)候next字段就發(fā)揮作用了它呀,它會(huì)把相同哈希值的鍵值對(duì)整理成一個(gè)單向鏈表結(jié)構(gòu)來方便查找螺男。

3. 字典

typedef struct dict {

    // 類型特定函數(shù)
    dictType *type;

    // 私有數(shù)據(jù)
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 當(dāng) rehash 不在進(jìn)行時(shí),值為 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在運(yùn)行的安全迭代器的數(shù)量
    int iterators; /* number of iterators currently running */

} dict;

type字段提供了一組類型的操作函數(shù):

typedef struct dictType {

    // 計(jì)算哈希值的函數(shù)
    unsigned int (*hashFunction)(const void *key);

    // 復(fù)制鍵的函數(shù)
    void *(*keyDup)(void *privdata, const void *key);

    // 復(fù)制值的函數(shù)
    void *(*valDup)(void *privdata, const void *obj);

    // 對(duì)比鍵的函數(shù)
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);

    // 銷毀鍵的函數(shù)
    void (*keyDestructor)(void *privdata, void *key);
    
    // 銷毀值的函數(shù)
    void (*valDestructor)(void *privdata, void *obj);

} dictType;

這組函數(shù)可以讓不同類型的鍵值對(duì)能夠使用不同的方法進(jìn)行復(fù)制纵穿、對(duì)比等操作下隧,真正讓redis實(shí)現(xiàn)了多態(tài)字典。

我們還可以見到谓媒,字典結(jié)構(gòu)體內(nèi)部包含了兩個(gè)哈希表的數(shù)組淆院,為什么需要兩個(gè)哈希表呢?這就引入了一個(gè)rehash的概念了句惯。

字典的rehash

當(dāng)哈希表中的哈希節(jié)點(diǎn)土辩,也就是鍵值對(duì)的數(shù)量越來越多或者越來越少的時(shí)候,原來分配的哈希表將會(huì)進(jìn)行伸縮宗弯,redis會(huì)維護(hù)一個(gè)哈希表的負(fù)載因子脯燃,其中計(jì)算方式是:負(fù)載因子=哈希表的使用數(shù)量/哈希表的長(zhǎng)度

當(dāng)符合以下兩個(gè)條件任何一個(gè)的時(shí)候就會(huì)進(jìn)行reshash操作:

  1. 服務(wù)器沒有執(zhí)行BGSAVE或者BGREWRITEAOF命令,并且這個(gè)負(fù)載因子大于等于1的時(shí)候蒙保。
  2. 服務(wù)器在執(zhí)行BGSAVE或者BGREWRITEAOF命令辕棚,并且負(fù)載因子超過5。

為什么在執(zhí)行備份命令的時(shí)候邓厕,負(fù)載因子要比沒有執(zhí)行備份的時(shí)候大呢逝嚎?原因就是redis在執(zhí)行BGSAVE之類的備份命令時(shí)候,會(huì)fork一個(gè)子進(jìn)程進(jìn)行備份详恼,而目前大部分操作系統(tǒng)都會(huì)采用copy on write也就是寫時(shí)復(fù)制的技術(shù)补君,如果此時(shí)過多的去進(jìn)行rehash會(huì)導(dǎo)致內(nèi)存占用過多。

rehash的步驟:

  1. 為哈希表數(shù)組ht[1]進(jìn)行空間分配昧互。分配的原則是:擴(kuò)容則分配ht[0].used*2挽铁,收縮則分配ht[0]*used/2伟桅。
  2. 重新計(jì)算ht[0]中的所有鍵值對(duì),然后逐步遷移到ht[1]中叽掘。
  3. 當(dāng)ht[0]所有鍵值對(duì)都遷移完畢之后楣铁,釋放ht[0]所占空間,把ht[1]取代到ht[0]的位置更扁,并且新創(chuàng)建一個(gè)空白的哈希表盖腕,也就是新的ht[1],整個(gè)rehash步驟到此結(jié)束浓镜。

漸進(jìn)式rehash

上面我們簡(jiǎn)單介紹了rehash的步驟溃列,但是如果真是那么簡(jiǎn)單去實(shí)現(xiàn)的話,其實(shí)是有缺陷的膛薛。試想想听隐,如果哈希表中擁有大量的鍵值對(duì),一次性去進(jìn)行rehash把大量的鍵值對(duì)進(jìn)行遷移相叁,勢(shì)必會(huì)導(dǎo)致性能低下遵绰,并且影響redis的讀寫性能甚至導(dǎo)致服務(wù)被停止。因此增淹,rehash是漸進(jìn)的椿访,并且是不能影響正常的redis讀寫服務(wù)的。以下就是漸進(jìn)式rehash的一個(gè)過程:

  1. ht[1]分配空間虑润,分配的規(guī)則上面已經(jīng)寫了成玫,這里就不再重復(fù)描述。
  2. 此時(shí)字典會(huì)擁有ht[0]ht[1]兩個(gè)哈希表拳喻,字段rehashidx就能產(chǎn)生作用了哭当。rehashidx默認(rèn)值是-1,當(dāng)它變?yōu)?說明rehash正式開始冗澈。
  3. rehash開始钦勘,redis會(huì)把ht[0]rehashidx索引中的鍵值對(duì)進(jìn)行rehashht[1],每次rehash完成都會(huì)讓rehashidx遞增1亚亲,然后重復(fù)這個(gè)過程彻采。
  4. 在這個(gè)過程中,我們難免會(huì)對(duì)redis進(jìn)行增刪改查的操作捌归,這個(gè)時(shí)候同時(shí)擁有兩個(gè)哈希表的作用就發(fā)揮出來了肛响。對(duì)于查找、刪除惜索、修改操作特笋,redis會(huì)現(xiàn)在ht[0]中進(jìn)行,如果找不到才會(huì)到ht[1]進(jìn)行對(duì)應(yīng)操作巾兆,而增加這些操作則會(huì)直接在ht[1]中進(jìn)行猎物,主要是讓ht[0]只減不增虎囚,這樣到了某個(gè)時(shí)刻,ht[0]的所有鍵值對(duì)一定會(huì)全部遷移到ht[1]蔫磨。

領(lǐng)悟

redis這個(gè)字典的結(jié)構(gòu)實(shí)現(xiàn)清晰明了溜宽,特別是其中的rehash機(jī)制很值得我去學(xué)習(xí),這個(gè)對(duì)于一些數(shù)據(jù)遷移并且不能影響正常服務(wù)的編程實(shí)現(xiàn)非常有借鑒意義质帅。

文章參考自:<<Redis設(shè)計(jì)與實(shí)現(xiàn)>> 第二版

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市留攒,隨后出現(xiàn)的幾起案子煤惩,更是在濱河造成了極大的恐慌,老刑警劉巖炼邀,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魄揉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拭宁,警方通過查閱死者的電腦和手機(jī)洛退,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杰标,“玉大人兵怯,你說我怎么就攤上這事∏患粒” “怎么了媒区?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掸犬。 經(jīng)常有香客問我袜漩,道長(zhǎng),這世上最難降的妖魔是什么湾碎? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任宙攻,我火速辦了婚禮,結(jié)果婚禮上介褥,老公的妹妹穿的比我還像新娘座掘。我一直安慰自己,他們只是感情好呻顽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布雹顺。 她就那樣靜靜地躺著,像睡著了一般廊遍。 火紅的嫁衣襯著肌膚如雪嬉愧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天喉前,我揣著相機(jī)與錄音没酣,去河邊找鬼王财。 笑死,一個(gè)胖子當(dāng)著我的面吹牛裕便,可吹牛的內(nèi)容都是我干的绒净。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼偿衰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼挂疆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起下翎,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤缤言,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后视事,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胆萧,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年俐东,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跌穗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡虏辫,死狀恐怖蚌吸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砌庄,我是刑警寧澤套利,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站鹤耍,受9級(jí)特大地震影響肉迫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稿黄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一喊衫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杆怕,春花似錦族购、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至互纯,卻和暖如春瑟幕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工只盹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辣往,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓殖卑,卻偏偏與公主長(zhǎng)得像站削,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子孵稽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351