Redis 字典的底層實現

字典又稱符號表,關聯數組或者映射,是一種用於保存鍵值對的抽象數據結構

一、Redis的字典底層結構

Redis 的字典使用哈希表作爲底層實現,一個哈希表裏面有多個哈希節點,而每個哈希表節點就保存了字典中的一個鍵值對,Redis的字典可以參照Java中的HashMap。

哈希表代碼

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

哈希表節點

typedef struct dictEntry{
     //鍵
     void *key;
     //值
     union{
          void *val;
          uint64_tu64;
          int64_ts64;
     }v;

     //指向下一個哈希表節點,形成鏈表
     struct dictEntry *next;
}dictEntry;


Redis的字典結構:
在這裏插入圖片描述

二、解決哈希衝突

1.哈希衝突

提到的哈希衝突,解決他的辦法有兩種,開放尋址法和拉鍊法,Redis 很明顯採用的是後者,後者很明顯更加清晰,存取更加方便,第一種數據不多還好,如果數據量特別大的時候,插入,查找,刪除,修改都是一件很困難的事情,時間複雜度太高。

2.rehash

隨着操作的不斷執行,哈希表保存的鍵值會逐漸地增多或者減少,爲了讓哈希表的負載因子維持在一個範圍之內,當哈希表存儲的鍵值對太多或者太少,程序要對哈希表的大小進行相應的擴展或者收縮。這就是我們要說的 rehash

3.那麼什麼時候纔會 rehash 呢?

  • 服務器目前沒有執行的 BGSAVE 命令或者 BGREWRUTEAOF 命令,並且哈希表的負載因子大於等於 1;
  • 服務器目前正在執行 BGSAVE 命令或者 BGREWRUTEAOF 命令,並且哈希表的負載因子大於等於 5;

負載因子 = 哈希表以保存的節點數量 / 哈希表的大小

BGSAVE,這個命令是 redis 進行 RDB 持久化時所用到的命令;BGREWRUTEAOF 就是另一種 AOF 持久化方式的工作方式,也就是在 Redis 進行持久化的時候,可能會觸發 rehash。

4.rehash 的實現原理

既然要進行重新散列,那麼原來的表肯定是不適合了,所以要重新開闢一張表,這就是上面我們所說的 ht [1], 之所以維持兩張表就是這個原因,如果需要擴容,那麼擴容後的長度也就是 ht [1].size=ht [0].used*2 的 2^n, 縮小的話就是 ht [1].size=ht [0].used 的 2^n, 準備條件好了就要開始移動了,直到 0 號哈希表中沒有保存的節點,這時候釋放空表的空間,將 ht [1] 更名爲 ht [0],然後將 ht [1] 置爲空,就完成了 rehash。

5.漸進式 rehash

如果像這樣 rehash 的話,如果你的字典中存儲這幾萬條,幾十萬條,幾百萬條的數據時,如果我們一次性的,集中式的把這些數據 rehash,那估計服務器就不能再進行其它服務了,高性能的 Redis 是絕對不允許這種事情發生的。所以Redis是通過漸進式rehash來保證性能的。

hash表有一個字段 rehashidx,索引計數器,記錄了 rehash 的進度。

以下是哈希表漸進式 rehash 的詳細步驟:

1)爲 ht [1] 分配空間, 讓字典同時持有 ht [0] 和 ht [1] 兩個哈希表。
2)在字典中維持一個索引計數器變量 rehashidx , 並將它的值設置爲 0 , 表示 rehash 工作正式開始
3)在 rehash 進行期間, 每次對字典執行添加、刪除、查找或者更新操作時, 程序除了執行指定的操作以外, 還會順帶將 ht [0] 哈希表在 rehashidx 索引上的所有鍵值對 rehash 到 ht [1] , 當 rehash 工作完成之後, 程序將 rehashidx 屬性的值增一。
4)隨着字典操作的不斷執行, 最終在某個時間點上, ht [0] 的所有鍵值對都會被 rehash 至 ht [1] , 這時程序將 rehashidx 屬性的值設爲 -1 , 表示 rehash 操作已完成。

6.注意:

因爲在進行 rehash 的時候,兩個表中都有值,所以不能確定具體在哪個表中,所以要在兩個表中進行 字典的刪除(delete)、查找(find)、更新(update)等操作,如果是查找的話,就會現在 ht [0] 中查找,沒有就去 ht [1] 中找,但是如果是增加的話,就會一律保存到 ht [1] 中,不會再像 ht [0] 中進行任何添加操作,不會多此一舉,保證 ht [0] 中的數據只減不增,直到他變成一個空表。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章