1. 程式人生 > 其它 >Redis的底層資料結構-字典

Redis的底層資料結構-字典

字典又稱為符號表或者關聯陣列、或對映(map),是一種用於儲存鍵值對的抽象資料結構。字典中的每一個鍵 key 都是唯一的,通過 key 可以對值來進行查詢或修改。C 語言中沒有內建這種資料結構的實現,所以字典依然是 Redis自己構建的。

  雜湊表結構定義:

typedef struct dictht{
     //雜湊表陣列
     dictEntry **table;
     //雜湊表大小
     unsigned long size;
     //雜湊表大小掩碼,用於計算索引值
     //總是等於 size-1
     unsigned long sizemask;
     //該雜湊表已有節點的數量
     unsigned long used;
 
}dictht

   雜湊表是由陣列 table 組成,table 中每個元素都是指向 dict.h/dictEntry 結構,dictEntry 結構定義如下:

typedef struct dictEntry{
     //鍵
     void *key;
     //值
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;
 
     //指向下一個雜湊表節點,形成連結串列
     struct dictEntry *next;
}dictEntry

  key 用來儲存鍵,val 屬性用來儲存值,值可以是一個指標,也可以是uint64_t整數,也可以是int64_t整數。

  注意這裡還有一個指向下一個雜湊表節點的指標,我們知道雜湊表最大的問題是存在雜湊衝突,如何解決雜湊衝突,有開放地址法和鏈地址法。這裡採用的便是鏈地址法,通過next這個指標可以將多個雜湊值相同的鍵值對連線在一起,用來解決雜湊衝突

  

  ①、雜湊演算法:Redis計算雜湊值和索引值方法如下:

#1、使用字典設定的雜湊函式,計算鍵 key 的雜湊值
hash = dict->type->hashFunction(key);
#2、使用雜湊表的sizemask屬性和第一步得到的雜湊值,計算索引值
index = hash & dict->ht[x].sizemask;

  ②、解決雜湊衝突:這個問題上面我們介紹了,方法是鏈地址法。通過字典裡面的 *next 指標指向下一個具有相同索引值的雜湊表節點。

  ③、擴容和收縮:當雜湊表儲存的鍵值對太多或者太少時,就要通過 rerehash(重新雜湊)來對雜湊表進行相應的擴充套件或者收縮。具體步驟:

      1、如果執行擴充套件操作,會基於原雜湊表建立一個大小等於 ht[0].used*2n 的雜湊表(也就是每次擴充套件都是根據原雜湊表已使用的空間擴大一倍建立另一個雜湊表)。相反如果執行的是收縮操作,每次收縮是根據已使用空間縮小一倍建立一個新的雜湊表。

      2、重新利用上面的雜湊演算法,計算索引值,然後將鍵值對放到新的雜湊表位置上。

      3、所有鍵值對都遷徙完畢後,釋放原雜湊表的記憶體空間。

  ④、觸發擴容的條件:

      1、伺服器目前沒有執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於1。

      2、伺服器目前正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於5。

    ps:負載因子 = 雜湊表已儲存節點數量 / 雜湊表大小。

  ⑤、漸近式 rehash

    什麼叫漸進式 rehash?也就是說擴容和收縮操作不是一次性、集中式完成的,而是分多次、漸進式完成的。如果儲存在Redis中的鍵值對只有幾個幾十個,那麼 rehash 操作可以瞬間完成,但是如果鍵值對有幾百萬,幾千萬甚至幾億,那麼要一次性的進行 rehash,勢必會造成Redis一段時間內不能進行別的操作。所以Redis採用漸進式 rehash,這樣在進行漸進式rehash期間,字典的刪除查詢更新等操作可能會在兩個雜湊表上進行,第一個雜湊表沒有找到,就會去第二個雜湊表上進行查詢。但是進行 增加操作,一定是在新的雜湊表上進行的。