1. 程式人生 > 資料庫 >Redis 基礎知識

Redis 基礎知識

資料型別

常用的包括**String、List、Hash、Set、Sorted Set**,不常用的包含GEO、Bitmap、HyperLogLog;底層資料結構包括簡單字串,雙向連結串列,陣列,壓縮陣列,雜湊表,跳錶;資料型別跟資料結構的對應關係為下圖所示;

img

hash演算法

  • 一個雜湊表就是一個數組,陣列中的每個元素稱為一個雜湊桶(Bucket);
  • 採用hash演算法進行鍵值對的儲存;
  • 一個鍵值對對應一個entry,entry裡包含鍵和 值的指標,並不儲存實際的值;如下圖所示:

img

  • 查詢值時,先通過鍵的值計算出hash值,再通過hash值以雜湊表陣列的長度取模,就能得到查詢的鍵的entry對應的下標;例如,假設hash表陣列的長度是1000,那麼在查詢鍵key所在的位置時,只需要計算key值的hash值,假設為12425475,那麼,key值所在的位置的下表即為12425475%1000=475,可直接通過下表獲取到key對應的entry。  

hash衝突與漸進式rehash

  • 什麼是hash衝突?

   hash表陣列長度是固定的,當使用hash演算法當儲存的資料量遠遠大於hash表的陣列長度時,就必定會有鍵計算出來的陣列下標相同。假設hash表陣列長度為10,key1的hash值為15,key2的hash值為25,那麼他們與10取模後都是5,即key1和key2都要儲存在下標為5的地方,這就是hash衝突。

  • 如何解決hash衝突?

  當發生hash衝突時,redis採用單向連結串列的方式儲存hash值,即在entry中增加next指標,指向下一個元素,比如上面的情況,key1已經儲存在了5的位置,那麼key2儲存時,會更新key1的next指標,指向key2。同樣是以上情況,如果key1和key2已經儲存了,需要查詢key2的值,則通過hash取模後得到的位置是5,再進行對比key1的值,如果不等於key2,那麼就通過key1的next指標,繼續對比下一個,直到找到為止。具體的資料結構圖下圖所示。

 img

  • 以上解決辦法帶來的問題?

  從上面的情況我們可以得知,當hash衝突時,需要通過連結串列一個個對比才能查詢到我們想要的key。如果是比較極端的情況,在某個hash桶位置的衝突鍵太多,就容易導致查詢效率低下,這就是hash衝突帶來的問題。

  • 如何解決?

  redis通過漸進式rehash來解決以上帶來的問題的。rehash操作就是增加現有桶的數量,讓逐漸增多的entry元素在更多的桶之間分散儲存,從而減少單個桶的衝突。我們可以先看一下redis儲存的一些原始碼:

 

typedef struct dictEntry {
void *key; //鍵
union {
void *val; //值
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; //指向下一個節點,形成連結串列
} dictEntry;

typedef struct dictht {
dictEntry **table; //雜湊表陣列,陣列的每個項是dictEntry連結串列的頭結點指標
unsigned long size; //雜湊表大小;在redis的實現中,size也是觸發擴容的閾值
unsigned long sizemask;//雜湊表大小掩碼,用於計算索引值;總是等於 size-1 
unsigned long used;//雜湊表中儲存的節點的數量
} dictht;

typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];//在字典內部,維護了兩張雜湊表。 一般情況下, 字典只使用 ht[0] 雜湊表, ht[1] 雜湊表只會在對 ht[0] 雜湊表進行 rehash 時使用
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;

 

觸發條件

1. 儲存元素與hash表長度的比例大於某個值(負載因子);
2. 單個桶儲存的元素大於某個值;

如何擴容

Redis 預設使用了兩個全域性雜湊表:雜湊表 1 和雜湊表 2。一開始,當你剛插入資料時,預設使用雜湊表 1,此時的雜湊表 2 並沒有被分配空間。隨著資料逐步增多,Redis 開始執行 rehash,這個過程分為三步:

1. 給雜湊表 2 分配更大的空間,例如是當前雜湊表 1 大小的兩倍;
2. 把雜湊表 1 中的資料重新對映並拷貝到雜湊表 2 中;
3. 釋放雜湊表 1 的空間。

漸進式rehash

以上擴容過程會帶來一個問題,當資料量較多時,這個過程比較耗時,那麼就必定會阻塞redis處理請求,所以redis採用漸進式rehash來解決這個問題,簡單來說,Redis 仍然正常處理客戶端請求,每處理一個請求時,從雜湊表 1 中的第一個索引位置開始,順帶著將這個索引位置上的所有 entries 拷貝到雜湊表 2 中;等處理下一個請求時,再順帶拷貝雜湊表 1 中的下一個索引位置的 entries。如下圖所示:

img