Redis數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)-字典(三)

字典又稱符號(hào)表, 關(guān)聯(lián)數(shù)組, 映射, 是一種用于保存鍵值對(duì)的抽象數(shù)據(jù)結(jié)構(gòu)

Redis構(gòu)建了自己的字典實(shí)現(xiàn), eg. set msg 'test'會(huì)構(gòu)建keymsg, valuetest的鍵值對(duì).

除了表示數(shù)據(jù)庫之外, 字典還是hash鍵的底層實(shí)現(xiàn)之一(另外一種是ziplist)

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

hash表

Redis字典所使用的哈希表由 dict.h/dictht 結(jié)構(gòu)定義

typedef struct dictht {
  dictEntry **table; // hash表數(shù)組
  unsigned long size; // hash表大小
  unsigned long sizemask; // hash表大小掩碼, 用于計(jì)算hash索引值, 總是=size-1
  unsigned long used; // 已有節(jié)點(diǎn)數(shù)量
}dictht;
空hash表結(jié)構(gòu)示意圖.png
hash表節(jié)點(diǎn)
typedef struct dictEntry {
  void *key; // 鍵
  union {
    void *val;
    unit64_tu64;
    int64_ts64;
  }v; // 值
  struct dictEntry *next;// 指向下個(gè)hash節(jié)點(diǎn)狠怨、形成鏈表
}

key屬性保存著鍵值對(duì)中的鍵、而v屬性則保存著鍵值對(duì)中的值, 它可以是一個(gè)指針, 或者是uint64_t類型的整數(shù), 或者是 int64_t的整數(shù)

next屬性是指向另一個(gè)節(jié)點(diǎn)的指針, 這個(gè)指針可以將多個(gè)hash值相同的鍵值對(duì)連接在一起, 解決鍵沖突問題

字典

Redis中的字典由 dict.h/dict 結(jié)構(gòu)定義

typedef struct dict {
  dictType *type; // 類型特定函數(shù)
  void *privdata; // 私有數(shù)據(jù)
  dictht ht[2]; // hash表
  in trehashidx; // rehash索引, 當(dāng)rehash不在進(jìn)行時(shí)、值為-1
} dict;

type屬性是一個(gè)指向 dictType結(jié)構(gòu)的指針, 每個(gè) dictType結(jié)構(gòu)保存了一簇用于操作特定類型鍵值對(duì)的函數(shù), Redis會(huì)為不同類型的字典設(shè)置不同類型的特定函數(shù)

privdata屬性則保存了需要傳給那些特定類型函數(shù)的可選參數(shù)

typeprivdata是針對(duì)不同類型的鍵值對(duì), 為創(chuàng)建多態(tài)字典設(shè)置的

typedef struct dictType {
  unsigned int (*hashFunction)(const void *key); // 計(jì)算hash值的函數(shù)
  void *(*keyDup)(void *privdata, const void *key); // 復(fù)制鍵的函數(shù)
  void *(valDup)(void *privdata, const void *obj); // 復(fù)制值的函數(shù)
  int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 對(duì)比鍵的函數(shù)
  void (*keyDestructor)(void *privdata, void *key); // 銷毀鍵的函數(shù)
  void (*valDestructor)(void *privdata, void *obj); // 銷毀值的函數(shù)
}dictType;

ht屬性是一個(gè)包含兩個(gè)項(xiàng)的數(shù)組, 數(shù)組中的每個(gè)項(xiàng)都是一個(gè)dictht哈希表, 一般只使用ht[0], ht[1]只會(huì)在對(duì)ht[0] 進(jìn)行rehash時(shí)使用.

rehashidx記錄當(dāng)前rehash的進(jìn)度, 若沒有在進(jìn)行rehash, 則值為 -1

完整的dict結(jié)構(gòu)
dict結(jié)構(gòu)示意圖.png

hash算法

Redis計(jì)算hash值的方式如下:

hash = dict->type->hashFunction(key); // 使用hash字典設(shè)置的hash函數(shù)衰齐、計(jì)算key的hash值
index = hash & dict->ht[x].sizemask; // 根據(jù)sizemask和hash值、計(jì)算出hash索引

解決鍵沖突

當(dāng)有兩個(gè)或以上數(shù)量的鍵被分配到hash表數(shù)組同一個(gè)索引上時(shí), 稱為鍵沖突 collision, Redis的hash表使用 鏈地址法(separate chaining)來解決沖突, 每個(gè)hash表節(jié)點(diǎn)上有一個(gè)next指針, 多個(gè)hash表節(jié)點(diǎn)使用next指針構(gòu)成單鏈表

dictEntry總是將新增節(jié)點(diǎn)添加到鏈表頭部(時(shí)間復(fù)雜度為 O(1)), 因?yàn)闆]有tail指針.

rehash

隨著操作的不斷進(jìn)行、hash表維護(hù)的鍵值對(duì)會(huì)增加或減少, 為了讓hash表的負(fù)載因子(load factor)維持在一個(gè)合理的額范圍, 當(dāng)hash表保存鍵值對(duì)數(shù)量太大或太小時(shí), 程序需要對(duì)hash表擴(kuò)容或縮容, 擴(kuò)容或縮容通過rehash進(jìn)行, Rehash步驟如下:

  1. 為字典的ht[1]hash表分配空間, hash表的空間大小取決于要執(zhí)行的操作, 及ht[0]當(dāng)前包含的鍵值對(duì)的數(shù)量(即: ht[0].used 屬性值), 若為擴(kuò)容, ht[1] 為第一個(gè)大于等于 (ht[0].used*2)d的*2n; 若為縮容, ht[1] 為第一個(gè)大于等于 (ht[0].used) 的 2n
  2. 將ht[0]中的所有鍵值對(duì)rehash到ht[1]上, rehash是指: 重新計(jì)算鍵的hash值和索引值, 然后將鍵值對(duì)放到ht[1]hash表的對(duì)應(yīng)位置
  3. 當(dāng)ht[0]包含的所有鍵值對(duì)遷移到ht[1]之后(ht[0]變?yōu)榭毡?, 釋放ht[0], 將ht[1]設(shè)置為 ht[0], 并在 ht[1]新創(chuàng)建一個(gè)空白hash表, 為下一次rehash做準(zhǔn)備
eg. 擴(kuò)容操作:
ht[0].used = 4, 4*2=8, 恰好是第一個(gè)大于等于4的2的n次方, 所以rehash后、ht[1]size為8

縮容操作:
ht[0].used = 4, 4=22, 恰好是第一個(gè)大于等于4的2的n次方, rehash后 ht[1].size = 4
hash表的擴(kuò)展與收縮

滿足下述任意條件時(shí), 會(huì)自動(dòng)擴(kuò)展:

  1. server 當(dāng)前未執(zhí)行bgsave 或者 bgrewriteaof, 且hash表的負(fù)載因子大于等于1

  2. server 當(dāng)前正在執(zhí)行bgsave 或者 bgrewriteaof, 且 hash表的負(fù)載因子大于等于 5

    (持久化時(shí)蚣录、優(yōu)先持久化操作)

load_factor = ht[0].used / ht[0].size; // 負(fù)載因子 = hash表已保存節(jié)數(shù)量 / hash表大小

bgsave或者bgrewriteaof執(zhí)行時(shí), Redis需要?jiǎng)?chuàng)建當(dāng)前服務(wù)器進(jìn)程的子進(jìn)程, 大多數(shù)操作系統(tǒng)使用寫時(shí)復(fù)制(copy-on-write)優(yōu)化子進(jìn)程使用效率, 所以子進(jìn)程存在期間, Server會(huì)提高rehash操作的負(fù)載因子, 盡可能避免此時(shí)rehash, 避免不必要的內(nèi)存寫入操作, 最大限度的節(jié)省內(nèi)存.

另外:

hash表的負(fù)載因子小于0.1時(shí), 程序會(huì)自動(dòng)縮容

漸進(jìn)式rehash

其實(shí), hash表的rehash操作、不是一次集中完成的, 而是多次迭代進(jìn)行的. 因?yàn)槿?ht[0]里保存的key有百萬計(jì), 甚至更多, 一次rehash導(dǎo)ht[1], 龐大的計(jì)算量可能會(huì)導(dǎo)致Server一段時(shí)間的停止服務(wù).

漸進(jìn)式rehash步驟如下:

  1. 為ht[1]分配空間, 讓字典同時(shí)持有ht[0] 和 ht[1]兩個(gè)hash表
  2. 在字典中維護(hù)一個(gè)索引計(jì)數(shù)器變量 rehashidx, 設(shè)置為0, 表示rehash已經(jīng)開始
  3. rehash期間, 每次對(duì)字典add, del, find or update操作, 程序除了執(zhí)行指定操作外, 還會(huì)順帶將 ht[0] hash表在 rehashidx索引上的所有鍵值對(duì) rehash 到 ht[1], rehash完成眷篇、將rehashidx++.
  4. 隨字典操作的不斷執(zhí)行萎河、某個(gè)時(shí)間點(diǎn), ht[0]的所有鍵值對(duì)rehash到ht[1], rehash完成、將 rehashidx設(shè)為 -1.

漸進(jìn)式rehash過程中會(huì)維護(hù) ht[0] 和 ht[1] 兩個(gè)hash表, 字典的刪除、查找虐杯、更新等操作都會(huì)在兩個(gè)hash表上進(jìn)行, eg. 查找: 會(huì)先在 ht[0] 上查找玛歌、若未找到則 到 ht[1] 上查找

但: 插入操作只會(huì)在 ht[1] 上操作

ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
字典API
函數(shù) 作用 時(shí)間復(fù)雜度
dictCreate 創(chuàng)建一個(gè)新的字典 O(1)
dictAdd 將給定鍵值對(duì)添加到字典 O(1)
dictReplace 添加or替換 O(1)
dictFetchValue 返回給定鍵的值 O(1)
dictGetRandomKey 隨機(jī)返回一個(gè)鍵值對(duì) O(1)
dictRelease 釋放給定字典, 及字典中包含的所有鍵值對(duì) O(N), N為值的數(shù)量

dict總結(jié)

  • 廣泛用于實(shí)現(xiàn)Redis的各種功能, 包括數(shù)據(jù)庫和hash鍵
  • 作為hash表的底層實(shí)現(xiàn), 每個(gè)字典有兩個(gè)hash表、一個(gè)平時(shí)使用, 一個(gè) rehash 時(shí)使用
  • 使用 Murmurhash2 計(jì)算key的hash值
  • 使用鏈地址法解決鍵沖突擎椰、同一個(gè)索引上的多個(gè)鍵值對(duì)連接成一個(gè)單鏈表
  • rehash是漸進(jìn)式完成支子、非一次性工作
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市达舒,隨后出現(xiàn)的幾起案子值朋,更是在濱河造成了極大的恐慌,老刑警劉巖休弃,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吞歼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡塔猾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門稽坤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丈甸,“玉大人,你說我怎么就攤上這事尿褪∧览蓿” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵杖玲,是天一觀的道長(zhǎng)顿仇。 經(jīng)常有香客問我,道長(zhǎng)摆马,這世上最難降的妖魔是什么臼闻? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮囤采,結(jié)果婚禮上述呐,老公的妹妹穿的比我還像新娘数焊。我一直安慰自己仔粥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布萨驶。 她就那樣靜靜地躺著代虾,像睡著了一般进肯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棉磨,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天江掩,我揣著相機(jī)與錄音,去河邊找鬼。 笑死频敛,一個(gè)胖子當(dāng)著我的面吹牛项郊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斟赚,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼着降,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了拗军?” 一聲冷哼從身側(cè)響起任洞,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎发侵,沒想到半個(gè)月后交掏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刃鳄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年盅弛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叔锐。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挪鹏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出愉烙,到底是詐尸還是另有隱情讨盒,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布步责,位于F島的核電站返顺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蔓肯。R本人自食惡果不足惜遂鹊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望省核。 院中可真熱鬧稿辙,春花似錦、人聲如沸气忠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旧噪。三九已至吨娜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淘钟,已是汗流浹背宦赠。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勾扭。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓毡琉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親妙色。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桅滋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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

  • 參考文獻(xiàn):黃健宏《Redis設(shè)計(jì)與實(shí)現(xiàn)》 定義 字典又稱為符號(hào)表,映射或關(guān)聯(lián)數(shù)組身辨,是一種用于保存鍵值對(duì)的抽象數(shù)據(jù)結(jié)...
    0愛上1閱讀 433評(píng)論 0 0
  • 正文 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu) ??字典丐谋,又稱為符號(hào)表(symbol table )、關(guān)聯(lián)數(shù)組(associative ana...
    于情于你閱讀 293評(píng)論 0 1
  • 上一篇文章詳細(xì)介紹了Redis的五種常用數(shù)據(jù)類型,每種數(shù)據(jù)類型在內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)都會(huì)因情況而異定庵。接下來吏饿,我會(huì)用幾篇...
    Javar閱讀 1,881評(píng)論 0 2
  • 1. 簡(jiǎn)介 字典又稱為符號(hào)表、關(guān)聯(lián)數(shù)組或映射洗贰,是一種保存鍵值對(duì)的抽象數(shù)據(jù)結(jié)構(gòu)找岖。在字典中,一個(gè)鍵(key)可以和一個(gè)...
    十年磨一劍1111閱讀 186評(píng)論 0 1
  • 三月敛滋,在某書房的公眾號(hào)上看到的推文,推薦理想國(guó)譯叢系列兴革,對(duì)于歷史和種族話題绎晃,一向不甚感興趣,但是跟隨圖圖大主教走進(jìn)...
    瑾瑾有敘閱讀 576評(píng)論 0 0