Redis 數據結構之字典(漸進式rehash)

Redis字典結構

哈希表結構

Redis字典使用的哈希表由dict.j/dictht結構定義,如下:

其中table屬性是一個數組,數組元素爲dict.h/dictEntry結構(key-value結構,類似於java中hashMap中的Node<K,V>結構)


 

🤔思考:結構中哈希表爲ht[2],容量爲2的dictht數組,爲什麼是2個?

ht[2]:一般情況下只會用到ht[0],ht[1]只會在ht[0]哈希表進行rehash的時候使用

rehashidx:記錄rehash目前的進度,如果目前沒有進行rehash,那麼它的值爲-1

未進行rehash結構如下

 

🤔思考:如何獲取值?

程序先根據鍵值對的鍵計算出哈希值和索引值,然後再根據索引值,將包含新鍵值對的哈希表節點放到哈希表數組的指定索引上面

Redis計算鍵的hash值使用MurmurHash2算法(注:優點在於即使輸入的鍵有規律,也能有很好的隨機分佈性,並且計算速度也很快,最新已到了MurmurHash3)

🤔思考:鍵衝突?如何解決?

當兩個或以上數量的鍵被分配到哈希表數組的統一索引上,就產生了鍵衝突,

Redis解決鍵衝突 使用的是 鏈表地址法(注:還有很多其他方案)

漸進式rehash

🤔思考:當數據量過於龐大,鏈表長度會非常長,那麼性能就會急劇下降(鏈表查詢效率低),怎麼辦?

因此爲了保證查詢的性能,redis再一定條件下會對字典的哈希表進行rehash(此時就用到了ht[1]),Redis中進行rehash的時候使用的是 漸進式rehash(爲了避免rehash對服務器性能造成影響,服務器並不是一次性將ht[0]裏面的鍵值對全部遷移到ht[1]中,而是漸進式的將ht[0]裏面的鍵值對慢慢rehash到ht[1]中),步驟如下

1 爲字典的ht[1]哈希表分配空間,該哈希表的空間大小取決於要執行的操作以及ht[0]當前包含的數量(一般是2的n次冪)

2.在字典中位置一個索引器變量rehashidx,並將它值設置爲0,表示rehash開始

3.將ht[0]中的所有鍵值對漸進式的rehash到ht[1]中:rehash指重新計算哈希值和索引值,並放置到ht[1]哈希表中

在rehash進行中,每次對字典進行添加、刪除或者更新的時候,程序除了執行指定的操作以外,還會順帶將鍵值對遷移到ht[1]中,當rehash工作完成之後,程序將rehashidx屬性增加一

4.隨着不斷的執行,最終全部遷移完成,將程序的rehashidx設置爲-1,表示完成

5當ht[0]包含的所有鍵值對都遷移到ht[1]中(ht[0]爲空表),釋放ht[0],當ht[1]設置爲ht[0],並再ht[1]新創建一個空白哈希表,爲下一次rehash做準備

注:因此有的時候會發現Redis內存暴增,有可能就是Redis 進行rehash操作

 

🤔思考:什麼情況下能進行rehash呢?

1.服務器目前沒有執行BGSAVE(RDB持久化)命令或者BGREWRITEAOF(AOF重寫)命令,並且哈希表的負載因子大於等於1

2.服務器目前正在執行BGSAVE或者BGREWRITEAOF命令,並且哈希表的負載因此大於等於5

負載因子 = 哈希表已保存的節點數量/哈希表的大小

🤔思考:爲什麼BGSAVE和BGREWRITEAOF命令執行與否,服務器要求負載因子不一樣?

因爲BGSAVE和BGREWRITEAOF執行的時候程序會創建子進程,而大多數操作系統都採用寫時複製(copy-on-write)技術來優化紫禁城的使用效率,提高負載因子,是爲了儘可能避免在子進程存在期間進行哈希表擴展操作,避免不必要的內存寫入操作,最大限制上節約內存

 

🤔思考:在進行rehash的時候,要對數據進行操作怎麼辦?

漸進式rehash期間操作:

(1)添加操作:在漸進式rehash期間,新添加的鍵值對一律會保存到ht[1]裏面,而ht[0]不進行任何添加操作,保證ht[0]只減不增。

(2)更新、刪除、查找操作:先去ht[0]查找並進行相關操作操作,再去ht[1]進行查找並進行相關操作,然後返回

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章