Redis一共支持5種數據結構,hash是其中的一種,在hash擴容的時候采用的是漸進式rehash的方式。要想深入理解漸進式rehash,首先要了解以下Redis中hash的數據結構。
哈希表節點
typedef struct dictEntry {
void *key; // 鍵
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; // 值
struct dictEntry *next; // 下一個節點
} dictEntry;
哈希表
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table; // 哈希表數組
unsigned long size; // 哈希表大小
unsigned long sizemask; // 掩碼,計算索引值,size-1
unsigned long used; // 哈希表已有節點的數量
} dictht;
字典
typedef struct dict {
dictType *type; // 類型特定函數
void *privdata; // 私有數據
dictht ht[2]; // 哈希表
// rehash索引
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
特定函數
typedef struct dictType {
// 計算哈希值的函數
uint64_t (*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;
字典中包含一個數據結構dictht
的ht
數組,一般情況下字典只是用ht[0]
用來存儲數據,ht[1]
在rehash時使用。
哈希算法原理
當向字典中添加一個元素時(假設此時 rehashidx = -1
,也就是沒有進行rehash),首先通過dict->type->hashFunction
計算該元素的hash
值,然后通過hash & dict->ht[x].sizemask
計算哈希地址index
。如果該元素對應的下標沒有數據,則直接添加,否則采用鏈地址法添加到hash對應index
元素的鏈表尾部。
rehash原理
隨着操作的不斷執行,哈希表中的元素會逐漸增加或者減少,為了讓哈希表的負載因子維持在一個合理的范圍內,程序需要對哈希表的大小進行相應的擴容和收縮。步驟如下:
-
為
ht[1]
哈希表分配空間。如果是擴容操作,ht[1]
的大小為第一個大於等於ht[0].used*2
的2
的n
次方冪,如果是收縮操作,ht[1]
的大小為第一個大於等於ht[0].used
的2
的n
次方冪 -
將保存在
ht[0]
中的所有鍵值對rehash到ht[1]
:rehash指的是重新計算鍵的哈希值和索引值,然后將鍵值對放到ht[1]
對應位置上 -
當
ht[0]
包含的所有鍵值對都遷移到ht[1]
之后,釋放ht[0]
,將ht[1]
設置為ht[0]
,並在ht[1]
新創建一個空白哈希表,為下一次rehash做准備
漸進式rehash原理
在擴容和收縮的時候,如果哈希字典中有很多元素,一次性將這些鍵全部rehash到ht[1]
的話,可能會導致服務器在一段時間內停止服務。所以,采用漸進式rehash的方式,詳細步驟如下:
-
為
ht[1]
分配空間,讓字典同時持有ht[0]
和ht[1]
兩個哈希表 -
將
rehashindex
的值設置為0
,表示rehash工作正式開始 -
在rehash期間,每次對字典執行增刪改查操作是,程序除了執行指定的操作以外,還會順帶將
ht[0]
哈希表在rehashindex
索引上的所有鍵值對rehash到ht[1]
,當rehash工作完成以后,rehashindex
的值+1
-
隨着字典操作的不斷執行,最終會在某一時間段上
ht[0]
的所有鍵值對都會被rehash到ht[1]
,這時將rehashindex
的值設置為-1
,表示rehash操作結束
漸進式rehash采用的是一種分而治之的方式,將rehash的操作分攤在每一個的訪問中,避免集中式rehash而帶來的龐大計算量。
需要注意的是在漸進式rehash的過程,如果有增刪改查操作時,如果index
大於rehashindex
,訪問ht[0]
,否則訪問ht[1]