Redis裏字典(dict)主要用在兩個場景:
一、保存數據庫的鍵值對
二、Hash類型的底層實現之一(另外一種是經過壓縮列表實現)數據庫
字典的定義以下:數組
/* * 字典 * * 每一個字典使用兩個哈希表,用於實現漸進式 rehash */ typedef struct dict { // 特定於類型的處理函數 dictType *type; // 類型處理函數的私有數據 void *privdata; // 哈希表(2 個) dictht ht[2]; // 記錄 rehash 進度的標誌,值爲 -1 表示 rehash 未進行 int rehashidx; // 當前正在運做的安全迭代器數量 int iterators; } dict;
(以上註釋來自《Redis設計與實現》)
哈希表dictht的定義以下:安全
/* * 哈希表 */ typedef struct dictht { // 哈希表節點指針數組(俗稱桶,bucket) dictEntry **table; // 指針數組的大小 unsigned long size; // 指針數組的長度掩碼,用於計算索引值 unsigned long sizemask; // 哈希表現有的節點數量 unsigned long used; } dictht;
哈希表用鏈地址法來解決碰撞。這裏table是個二級指針,每一個指針指向了對應索引的節點連成的鏈表。
哈希表節點定義以下:數據結構
/* * 哈希表節點 */ typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 鏈日後繼節點 struct dictEntry *next; } dictEntry;
哈希表的性能取決於大小(dictht的size屬性)與保存節點數量(dictht的used屬性)之間的比率:
一、哈希表的大小與節點數量,比率在 1:1 時,哈希表的性能最好;
二、若是節點數量比哈希表的大小要大不少的話,那麼哈希表就會退化成多個鏈表,哈希表自己的性能優點便不復存在。函數
之因此前面定義dict的時候,用了兩個哈希表dictht,是由於,在哈希表的節點比較多的時候,在第一個哈希表ht[0]上查找比較慢,這時,就須要對哈希表從新調整,也就是rehash。
關於rehash,有如下幾點須要注意:
一、建立一個更大的哈希表,而後賦給ht[1],將ht[0]上的節點移動到ht[1]上,移動結束後,將ht[1]設置爲ht[0]。
二、rehash分主動和被動,主動是指,Redis的常規服務定時去檢查而且rehash,被動是指,用戶在添加新鍵值對時觸發rehash。
三、當哈希表比較大的時候(表大,節點多),進行rehash可能會比較慢,這個時候,rehash不是一步阻塞完成的,是「漸進式」的,也就是分幾回完成,也就是,我此次移動1個索引的所有節點,下次常規服務裏移動另外一個索引的節點,以這種方式來完成rehash。
四、在rehash的過程當中,會同時使用到兩個哈希表,新添加進來的節點是放在ht[1]上。我的猜想是由於,ht[1]是rehash的最終結果,若是添加在ht[0]上,還須要多作一步,把ht[0]上的移動到ht[1]上。
五、在查找刪除的時候,會同時操做ht[0]和ht[1],由於在rehash的過程當中,哈希表分佈在這兩個表上。性能
當比值 used*100/size 小於10的時候,也就是字典的填充率小於10%的時候,就收縮字典,收縮過程與rehash過程相似,不過ht[1]比ht[0]小。收縮是由常規服務檢查觸發的,用戶不會觸發收縮。ui
參考:
一、《Redis設計與實現》設計