Redisbook學習筆記(1)字典(2)

添加新元素到空白字典數據庫

當第一次往空字典裏添加鍵值對時,程序會根據dict.h/DICT_HT_INITIAL_SIZE 裏指定的大ide

小爲d->ht[0]->table 分配空間(在目前的版本中,DICT_HT_INITIAL_SIZE 的值爲4 )。性能

如下是字典空白時的樣子:spa

wKioL1LeNkuDRY-vAADzqf7qGUk801.jpg

如下是往空白字典添加了第一個鍵值對以後的樣子:3d

wKioL1LeNuPR-E3LAADXuS1hG-Q267.jpg

添加新鍵值對時發生碰撞處理blog

在哈希表實現中,當兩個不一樣的鍵擁有相同的哈希值時,咱們稱這兩個鍵發生碰撞(collision),索引

而哈希表實現必須想辦法對碰撞進行處理。進程

字典哈希表所使用的碰撞解決方法被稱之爲鏈地址法:這種方法使用鏈表將多個哈希值相同的內存

節點串連在一塊兒,從而解決衝突問題。get

假設如今有一個帶有三個節點的哈希表,以下圖:

wKiom1LeODPA0Q62AAEMhwqJICw922.jpg

對於一個新的鍵值對key4 和value4 ,若是key4 的哈希值和key1 的哈希值相同,那麼它們

將在哈希表的0 號索引上發生碰撞。

經過將key4-value4 和key1-value1 兩個鍵值對用鏈表鏈接起來,就能夠解決碰撞的問題:

wKiom1LeOGWRW0kyAADDrZ9addo411.jpg

添加新鍵值對時觸發了rehash 操做

對於使用鏈地址法來解決碰撞問題的哈希表dictht 來講,哈希表的性能依賴於它的大小(size

屬性)和它所保存的節點的數量(used 屬性)之間的比率:

比率在1:1 時,哈希表的性能最好;

若是節點數量比哈希表的大小要大不少的話,那麼哈希表就會退化成多個鏈表,哈希表

自己的性能優點就再也不存在;

舉個例子,對於下面這個哈希表,平均每次失敗查找只須要訪問1 個節點(非空節點訪問2

次,空節點訪問1 次):

wKiom1LeOL7yCL5DAADMomrjS1Q913.jpg

而對於下面這個哈希表,平均每次失敗查找須要訪問5 個節點:

wKioL1LeOMahzirhAAF3fRlgyBE316.jpg

爲了在字典的鍵值對不斷增多的狀況下保持良好的性能,字典須要對所使用的哈希表(ht[0])

進行rehash 操做:在不修改任何鍵值對的狀況下,對哈希表進行擴容,儘可能將比率維持在1:1

左右。

dictAdd 在每次向字典添加新鍵值對以前,都會對哈希表ht[0] 進行檢查,對於ht[0] 的

size 和used 屬性,若是它們之間的比率ratio = used / size 知足如下任何一個條件的話,

rehash 過程就會被激活:

1. 天然rehash :ratio >= 1 ,且變量dict_can_resize 爲真。

2. 強制rehash : ratio 大於變量dict_force_resize_ratio (目前版本中,

dict_force_resize_ratio 的值爲5 )。

Note: 何時dict_can_resize 會爲假?

在前面介紹字典的應用時也說到過,一個數據庫就是一個字典,數據庫裏的哈希類型鍵

也是一個字典,當Redis 使用子進程對數據庫執行後臺持久化任務時(好比執行BGSAVE

或BGREWRITEAOF 時), 爲了最大化地利用系統的copy on write 機制, 程序會暫時將

dict_can_resize 設爲假,避免執行天然rehash ,從而減小程序對內存的觸碰(touch)。

當持久化任務完成以後,dict_can_resize 會從新被設爲真。

另外一方面,當字典知足了強制rehash 的條件時,即便dict_can_resize 不爲真(有BGSAVE

或BGREWRITEAOF 正在執行),這個字典同樣會被rehash 。


Rehash 執行過程

字典的rehash 操做實際上就是執行如下任務:

1. 建立一個比ht[0]->table 更大的ht[1]->table ;

2. 將ht[0]->table 中的全部鍵值對遷移到ht[1]->table ;

3. 將原有ht[0] 的數據清空,並將ht[1] 替換爲新的ht[0] ;

通過以上步驟以後,程序就在不改變原有鍵值對數據的基礎上,增大了哈希表的大小。

做爲例子,如下四個小節展現了一次對哈希表進行rehash 的完整過程。

1. 開始rehash

這個階段有兩個事情要作:

1. 設置字典的rehashidx 爲0 ,標識着rehash 的開始;

2. 爲ht[1]->table 分配空間,大小至少爲ht[0]->used 的兩倍;

這時的字典是這個樣子:

wKioL1LeOgmQGnW8AAF6VXf7duI503.jpg

Rehash 進行中

在這個階段,ht[0]->table 的節點會被逐漸遷移到ht[1]->table ,由於rehash 是分屢次進

行的(細節在下一節解釋),字典的rehashidx 變量會記錄rehash 進行到ht[0] 的哪一個索引

位置上。

如下是rehashidx 值爲2 時,字典的樣子:

wKiom1LeOmSx2BMrAAFvvvUPVvI091.jpg

注意除了節點的移動外,字典的rehashidx 、ht[0]->used 和ht[1]->used 三個屬性也產生

了變化。

3. 節點遷移完畢

到了這個階段,全部的節點都已經從ht[0] 遷移到ht[1] 了:

wKioL1LeOnzSU70qAAF1EXXNbXk453.jpg

4. Rehash 完畢

在rehash 的最後階段,程序會執行如下工做:

1. 釋放ht[0] 的空間;

2. 用ht[1] 來代替ht[0] ,使原來的ht[1] 成爲新的ht[0] ;

3. 建立一個新的空哈希表,並將它設置爲ht[1] ;

4. 將字典的rehashidx 屬性設置爲-1 ,標識rehash 已中止;

如下是字典rehash 完畢以後的樣子:

wKiom1LeOxDyUrlHAAFEhOItD0g855.jpg

對比字典rehash 以前和rehash 以後,新的ht[0] 空間更大,而且字典原有的鍵值對也沒有被

修改或者刪除。

相關文章
相關標籤/搜索