介紹
Redis的雜湊字典通過key值來找對應的value。需要注意的是Redis的字典是如何進行rehash的。
源碼dict.h dict.c資料結構
如所示,雜湊字典用dict結構體表示,其中含有兩個雜湊表,主要用於進行rehash操作。同時雜湊表使用量表的方式解決衝突。具體的資料結構如下:
/* * 雜湊表節點 */typedef struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; // 鏈往後繼節點 struct dictEntry *next; } dictEntry;/* * 特定於類型的一簇處理函數 */typedef struct dictType { // 計算鍵的雜湊值函數 unsigned int (*hashFunction)(const void *key); // 複製鍵的函數 void *(*keyDup)(void *privdata, const void *key); // 複製值的函數 void *(*valDup)(void *privdata, const void *obj); // 對比兩個鍵的函數 int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 鍵的釋構函數 void (*keyDestructor)(void *privdata, void *key); // 值的釋構函數 void (*valDestructor)(void *privdata, void *obj);} dictType;/* * 雜湊表 */typedef struct dictht { // 雜湊表節點指標數組(俗稱桶,bucket) dictEntry **table; // 指標數組的大小 unsigned long size; // 指標數組的長度掩碼,用於計算索引值 unsigned long sizemask; // 雜湊表現有的節點數量 unsigned long used; } dictht;/* * 字典 * * 每個字典使用兩個雜湊表,用於實現漸進式 rehash */typedef struct dict { // 特定於類型的處理函數 dictType *type; // 類型處理函數的私人資料 void *privdata; // 雜湊表(2個) dictht ht[2]; // 記錄 rehash 進度的標誌,值為-1 表示 rehash 未進行 int rehashidx; // 當前正在運作的安全迭代器數量 int iterators; } dict;/* * 字典迭代器 * * 如果 safe 屬性的值為 1 ,那麼表示這個迭代器是一個安全迭代器。 * 當安全迭代器正在迭代一個字典時,該字典仍然可以調用 dictAdd 、 dictFind 和其他函數。 * * 如果 safe 屬性的值為 0 ,那麼表示這不是一個安全迭代器。 * 如果正在運作的迭代器是不安全迭代器,那麼它只可以對字典調用 dictNext 函數。 */typedef struct dictIterator { // 正在迭代的字典 dict *d; int table, // 正在迭代的雜湊表的號碼(0 或者 1) index, // 正在迭代的雜湊表數組的索引 safe; // 是否安全? dictEntry *entry, // 當前雜湊節點 *nextEntry; // 當前雜湊節點的後繼節點} dictIterator;
分析
rehash
在字典中使用了兩個hash表就是為了方便進行rehash的,rehash主要有兩種方式,一是由後台調用cron進行按照100步進進行hash,二是在進行添加刪除字典中的元素的時候進行1步的hash。
這裡每一次的hash的步進是以真箇hash key對應的鏈表為單位的,也就是說1步進的rehash是將原來hash表中的一個key鏈表進行rehash到新的雜湊表中的位置。這裡可以看出Redis在此處是犧牲了記憶體但是換來了效能的響應。將rehash進行分散操作可以避免阻塞導致的效能急劇下降。
迭代器
迭代器的屬性如果是安全的則表明可以在迭代的過程中進行dictAdd等其他的操作,而如果迭代器是不安全的則只能進行next的操作。
同時Redis限制在有迭代器訪問字典的時候進行rehash,因為這樣會造成對一個元素訪問多次。但是在rehash的過程中可以隨時的對字典進行遍曆,一旦迭代器開始遍曆字典了,rehash就會暫停知道沒有迭代器在對字典進行遍曆為止。