dict
-
結構
// 哈希表結構 typeof struct dictht { // 哈希表數組 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用於計算索引值, 總是等於 size - 1 unsigned long sizemask; // 該哈希表已有節點的數量 unsigned long used; } // 哈希節點結構 typeof struct dictEntry { // 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; double d; } v; // 指向下一個哈希表節點,形成鏈表 // 該指針可以將多個哈希值相同的鍵值對連接在一起,以此來解決 hash 衝突的問題 struct dictEntry *next; } dictEntry; // dict 結構 typeof struct dict { // 類型特定函數 dictType *type; // 私有數據 void *privdata; // 哈希表 dictht ht[2]; // rehash索引,當rehash不在進行時值爲-1 long rehashidx; }
-
說明
dict 中的 type 屬性和 privdata 屬性是針對不同類型的鍵值對,爲創建多態字典而設置的;
type 屬性指向的是 dictType 結構的指針,它保存了一簇用於操作特定類型鍵值對的函數,而 privdata 則是保存用於傳給這些函數的可選參數。
ht[1] 只有對 ht[0] 進行 rehash 的過程是纔會被使用。
rehashidx 記錄了 rehash 的進度。 -
漸進式 rehash
因爲對內存擴容操作會涉及到數據的遷移操作O(N),對 redis 很難承受這樣耗時的過程(大字典表)。
因此採取了 rehash 操作,其過程爲:1. 主動 rehash(dictRehashMilliseconds 服務器定時任務) 或 被動 rehash(_dictRehashStep 負責)時,生成一個新的 ht[1], 並設置 rehashidx 爲 0。 2. rehash 期間當有新鍵值時,將添加到 ht[1] 中;搜索會先從 ht[0] 中查,如果找不到再從 ht[1] 中查。 3. rehash 過程是漸進的,默認情況下,單次最少轉移空桶數量爲 10 次(版本 5.0)。 4. 當 rehash 過程完全結束,那麼修改 ht[0] 指針引用,讓他指向新的字典表 ht[1],並設置 rehashidx 爲 -1,標記整個字典 rehash 結束。 5. 需要注意的是,每次 CURD 操作時,如果當前爲 rehash 狀態,需要去完成一個桶的轉移,然後才能返回(參考 dictAddRaw)。
-
擴容條件
正常情況下,當 hash 表中元素的個數等於第一維數組的長度時,就會開始擴容,擴容的新數組是原數組大小的 2 倍。
不過如果 Redis 正在做 bgsave,爲了減少內存頁的過多分離 (Copy On Write),Redis 儘量不去擴容 (dict_can_resize),
但是如果 hash 表已經非常滿了,元素的個數已經達到了第一維數組長度的 5 倍 (dict_force_resize_ratio),說明 hash 表已經過於擁擠了,這個時候就會強制擴容。
沒有在執行 BGSAVE 或 BGREWRITEAOF 命令時,負載因子大於1進行擴容
在執行 BGSAVE 或 BGREWRITEAOF 命令時,負載因子大於5進行擴容
拓展操作給 ht[1] 分配第一個大於等於 ht[0].used*2 的 2 的 n 次方冪的空間 -
縮容條件
當 hash 表因爲元素的逐漸刪除變得越來越稀疏時,Redis 會對 hash 表進行縮容來減少 hash 表的第一維數組空間佔用。縮容的條件是元素個數低於數組長度的 10%。縮容不會考慮 Redis 是否正在做 bgsave。
負載因子小於 0.1 時進行縮容
縮容操作給 ht[1] 分配第一個大於等於 ht[0].used 的 2 的 n 次方冪的空間 -
set 的結構
set 的結構底層實現也是字典,只不過所有的 value 都是 NULL,其它的特性和字典一模一樣。