字典

【1758872】的博客發表於2024-09-27

應用場景

//Redis資料庫的key-value和帶有過期時間的key都是一個字典
struct RedisDb {
    dict* dict; // all keys  key=>value
    dict* expires; // all expired keys key=>long(timestamp)
    ...
}
//zset中儲存value和score值的對映關係也是通過dict結構實現的
struct zset {
    dict *dict; // all values  value=>score
    zskiplist *zsl;
}

set的結構底層實現也是字典,所有的value為null

dict內部結構

//dict結構存在兩張雜湊表,通常情況下只有一個有值
//Rehash過程中,分別儲存舊的雜湊表和新的雜湊表
struct dict {
    ...
    dictht ht[2];
}
struct dictEntry {
    void* key;
    void* val;
    dictEntry* next; // 連結下一個 entry
}
struct dictht {
    dictEntry** table; // 二維
    long size; // 第一維陣列的長度
    long used; // hash 表中的元素個數
    ...
}

在這裡插入圖片描述

漸進式rehash

大字典的擴容是比較耗時間的,需要重新申請陣列,然後把舊字典的所有元素遷移到新字典,是一個O(N)級別的操作,單執行緒的Redis很難接受這種耗時的操作。所以採用小步搬遷的方式

  • 搬遷操作存在於一些指令中
  • 定時任務搬遷

雜湊函式

hashtable 的效能好不好完全取決於 hash 函式的質量。

hash 函式如果可以將 key 打散的比較均勻,那麼這個 hash 函式就是個好函式。Redis 的字典預設的 hash 函式是 siphash。siphash 演算法即使在輸入 key 很小的情況下,也可以產生隨機性特別好的輸出,而且它的效能也非常突出。對於 Redis 這樣的單執行緒來說,字典資料結構如此普遍,字典操作也會非常頻繁,hash 函式自然也是越快越好。

雜湊攻擊:如果 hash 函式存在偏向性,黑客就可能利用這種偏向性對伺服器進行攻擊。存在偏向性的 hash 函式在特定模式下的輸入會導致 hash 第二維連結串列長度極為不均勻,甚至所有的元素都集中到個別連結串列中,直接導致查詢效率急劇下降,從O(1)退化到O(n)。

擴容條件

正常情況下,當 hash 表中元素的個數等於第一維陣列的長度時,就會開始擴容,擴容的新陣列是原陣列大小的 2 倍。不過如果 Redis 正在做 bgsave,為了減少記憶體頁的過多分離 (Copy On Write),Redis 儘量不去擴容 (dict_can_resize),但是如果 hash 表已經非常滿了,元素的個數已經達到了第一維陣列長度的 5 倍 (dict_force_resize_ratio),說明 hash 表已經過於擁擠了,這個時候就會強制擴容。

縮容條件

當 hash 表因為元素的逐漸刪除變得越來越稀疏時,Redis 會對 hash 表進行縮容來減少 hash 表的第一維陣列空間佔用。縮容的條件是元素個數低於陣列長度的 10%。縮容不會考慮 Redis 是否正在做 bgsave。