【學習Redis系列】Redis中字典的實現


                                               

redis由c語言編寫,不含內置的字典數據結構,redis自己實現了字典數據結構,redis服務器中的數據庫使用的底層數據結構就是字典,

哈希鍵在某些特定情況下也會使用字典作為底層設計。

(特定:哈希對象的編碼可以是ziplist或hashtable,

  1.哈希對象保存的所有鍵值對的鍵和值得字符串長度都小於64字節,

  2.哈希對象保存的鍵值對數量小於512個, 不能滿足這兩個條件的哈希對象使用hashtable編碼

截圖代碼版本3.0.504


 

1.哈希表與哈希節點的關系:

Redis字典由哈希表構成,哈希表由哈希節點構成. 參考源碼 dict.h

 

 

 圖1-1所示結構,為一個初始大小為4的空的哈希表.

此時假設向其中存入鍵值對A,保存后如圖1-2所示,具體如何保存到索引位置上,后文有寫.


 

2.Redis字典與哈希表、哈希節點的關系

 

 

 字典中ht保存了兩個哈希表,一般情況下,字典使用ht[0]哈希表,h[1]哈希表只有在對ht[0]rehash時使用.rehashidx通常為-1,如果正在進行rehash,則值大於-1.

type指向的dictType結構提供了特定類型的函數,privdata為特定類型函數的可選參數. 比如計算鍵的哈希值時,使用hash=dict->type->hashfunction(key);

 

 

 如圖1-3 為一個字典的結構,字典中保存了一個元素。


 

3.如何將元素添加到Redis字典中

  元素A是如何保存的, 計算元素A的哈希值 :hash = dict->type->hashfunction(元素A的key) 

  通過哈希表中的sizemask與hash值,計算出索引值  : index = hash & dict->ht[0].sizemask (不發生rehash時,為ht[0],發生rehash時,可能為ht[0]或ht[1]),

  若此時再加入元素B,且元素B最終計算出的索引值與元素A相同,則將B插入A的前面。如圖1-4

 

 

TIPs(

  初學哈希表時疑問:為什么哈希表中的sizemask值為 size-1? 為什么索引值是通過hash & sizemask?以及為何哈希表的大小都為2的冪? 

  通過&運算計算出當前哈希表大小(0~size-1)范圍內的索引值,通過2的冪保證了索引值的均勻分布,

  比如哈希表大小為16,則sizemask為15 ,sizemask二進制為1111,此時只要保證hash值均勻分布就能保證索引值的均勻分布。

)

Redis將字典作為數據庫底層實現時,使用的Murmurhash計算鍵的哈希值。


 

4.rehash的執行

1)何時開始rehash操作

  字典中的哈希表隨着保存元素越來越多,當負載因子load_factor = ht[0].used / ht[0].size 滿足某些值時,開始對哈希表執行擴展操作

  具體情況如下:

    a.redis服務器目前正在進行BGSAVE 或BGREWRITEAOF命令,則load_factor的負載因子大於等於5,則開始擴展

    b.redis服務器沒有進行BGSAVE 或BGREWRITEAOF命令,則load_factor的負載因子大於等於1,則開始擴展

  服務器進行BGSAVE或BGREWRITEAOF命令時,創建子進程執行命令,此時采用寫時復制技術優化子進程效率,所以此時負載因子調大,避免執行擴展操作,節約內存。

2)rehash流程

  a.當rehash執行擴容時,為ht[1]分配空間,具體分配多大空間呢? ht[1]的大小為第一個大於等於ht[0].used x 2的2的n次冪  舉個栗子:此時used 為 8,則擴容應分配16<= 2的n次冪  所以16就滿足。

     當rehash執行收縮時,分配空間為 h[1]的大小為第一個大於等於ht[0].used的2的n次冪  舉個栗子:此時used為8,則2的3次冪就滿足要求 ,即ht[1]空間為8

  b.空間分配之后,就是元素的重新哈希,將h[0]中的元素 重新哈希計算 添加到h[1]中,並且從h[0]中刪除

  c.全部元素都遷移完成后釋放ht[0],將ht[1]設置為ht[0],並在ht[1]重新創建一個空白哈希表,為下一次hash做准備

3)rehash並不是一次就完成的

  rehash操作如果一次處理幾百萬個 幾千萬個 或更多的鍵值對,那服務器就不用干別的了,這段時間都沒得服務了。避免這種大批量的rehash,

  redis采用漸進式,一次太多,那就分多次,還記得那個rehashidx吧,平時閑着為-1,開始rehash后,rehashid設置為0,標志着從ht[0]哈希表的索引0開始進行rehash。

  索引0上的鍵值對都rehash完成后,rehashidx值+1.繼續下一索引值的rehash。

  so完成的rehash過程 :

    a.為ht[1]分配空間

    b.將rehashidx設置為0,開始rehash

    c.rehash期間,對字典的CRUD操作 還會順帶將rehashidx索引上的鍵值對rehash到ht[1]上.(新鍵值對的添加只會在ht[1]上操作,其余操作會在兩個哈希表都進行操作)

    d.隨着操作不斷進行,某個時間點全部rehash結束,將rehashidx值設置為-1,表示完成這次rehash。

  分而治之的漸進式rehash避免了集中式rehash帶來的巨大計算量。


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM