Redis基礎知識

LZC發表於2021-07-08

Redis為什麼這麼快

  • 記憶體資料庫,所有的操作都是在記憶體中完成,記憶體的訪問速度很快。
  • 使用了高效的資料結構。

Redis底層資料結構一共有 6 種,分別是簡單動態字串、雙向連結串列、壓縮列表、雜湊表、跳錶和整數陣列。它們和資料型別的對應關係如下圖所示:

Redis全域性雜湊表

為了實現從鍵到值的快速訪問,Redis使用了雜湊表來儲存所有的鍵值對。

一個雜湊表,其實就是一個陣列,陣列的每個元素稱為一個雜湊桶。所以,我們常說,一個雜湊表是由多個雜湊桶組成的,每個雜湊桶中儲存了鍵值對資料。

我們只需要計算鍵的雜湊值,就可以知道它所對應的雜湊桶位置,然後就可以訪問相應的 entry 元素。

雜湊衝突

Redis 解決雜湊衝突的方式,就是鏈式雜湊。鏈式雜湊也很容易理解,就是指同一個雜湊桶中的多個元素用一個連結串列來儲存,它們之間依次用指標連線。

rehash 操作

如果雜湊表寫入的資料越來越多,那麼雜湊衝突也會越來越多,這會導致雜湊桶上的連結串列長度過長而導致這條鏈上的元素查詢耗時變長,使得效率降低。

rehash 操作就是對雜湊桶進行擴容,增加雜湊桶數量,減少雜湊衝突,這樣就能減少單個桶中的元素數量。

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

  1. 給雜湊表 2 分配更大的空間,例如是當前雜湊表 1 大小的兩倍;

  2. 把雜湊表 1 中的資料重新對映並拷貝到雜湊表 2 中;

  3. 釋放雜湊表 1 的空間。

這個過程看似簡單,但是第二步涉及大量的資料拷貝,如果一次性把雜湊表 1 中的資料都遷移完,會造成 Redis 執行緒阻塞,無法服務其他請求。此時,Redis 就無法快速訪問資料了。

為了避免這個問題,Redis 採用了漸進式 rehash。

漸進式 rehash

簡單來說就是在第二步拷貝資料時,Redis 仍然正常處理客戶端請求,每處理一個請求時,從雜湊表 1 中的第一個索引位置開始,順帶著將這個索引位置上的所有 entries 拷貝到雜湊表 2 中;等處理下一個請求時,再順帶拷貝雜湊表 1 中的下一個索引位置的 entries。如下圖所示:

這樣就巧妙地把一次性大量拷貝的開銷,分攤到了多次處理請求的過程中,避免了耗時操作,保證了資料的快速訪問。

漸進式rehash執行時,除了根據鍵值對的操作來進行資料遷移,Redis本身還會有一個定時任務在執行rehash,如果沒有鍵值對操作時,這個定時任務會週期性地(例如每100ms一次)搬移一些資料到新的雜湊表中,這樣可以縮短整個rehash的過程。

壓縮列表實際上類似於一個陣列,陣列中的每一個元素都對應儲存一個資料。和陣列不同的是,壓縮列表在表頭有三個欄位 zlbytes、zltail 和 zllen,分別表示列表長度、列表尾的偏移量和列表中的 entry 個數;壓縮列表在表尾還有一個 zlend,表示列表結束。

在壓縮列表中,如果我們要查詢定位第一個元素和最後一個元素,可以通過表頭三個欄位的長度直接定位,複雜度是 O(1)。而查詢其他元素時,就沒有這麼高效了,只能逐個查詢,此時的複雜度就是 O(N) 了。

有序連結串列只能逐一查詢元素,導致操作起來非常緩慢,於是就出現了跳錶。具體來說,跳錶在連結串列的基礎上,增加了多級索引,通過索引位置的幾個跳轉,實現資料的快速定位,如下圖所示:

可以看到,這個查詢過程就是在多級索引上跳來跳去,最後定位到元素。這也正好符合“跳”表的叫法。當資料量很大時,跳錶的查詢複雜度就是 O(logN)。

本作品採用《CC 協議》,轉載必須註明作者和本文連結