1. 程式人生 > 其它 >Redis有哪些慢操作

Redis有哪些慢操作

redis 資料型別與底層資料結構的關係


可以看到,String 型別的底層實現只有一種資料結構,也就是簡單動態字串。而 List、Hash、Set 和 Sorted Set 這四種資料型別,都有兩種底層實現結構。通常情況下,我們會把這四種類型稱為集合型別,它們的特點是一個鍵對應了一個集合的資料。redis 3.2後引入了quicklist結構

鍵和值用什麼結構組織

Redis 使用了一個雜湊表來儲存所有鍵值對。
一個雜湊表,其實就是一個數組,陣列的每個元素稱為一個雜湊桶。所以,我們常說,一個雜湊表是由多個雜湊桶組成的,每個雜湊桶中儲存了鍵值對資料。
雜湊桶中的元素儲存的並不是值本身,而是指向具體值的指標。


潛在的風險點:

  1. 雜湊表的衝突問題
  2. rehash 可能帶來的操作阻塞

為什麼雜湊表操作變慢了?


當雜湊衝突變多後,將雜湊遍歷退化成連結串列遍歷,導致查詢變慢,這時需要rehash來重建雜湊表。
Redis 會對雜湊表做 rehash 操作, 為了使 rehash 操作更高效,Redis 預設使用了兩個全域性雜湊表:

  1. 雜湊表 1
  2. 雜湊表 2

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

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

漸進式 rehash

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

  1. 對於 String 型別來說,找到雜湊桶就能直接增刪改查了,所以,雜湊表的 O(1) 操作複雜度也就是它的複雜度了。
  2. 對於集合型別來說,即使找到雜湊桶了,還要在集合中再進一步操作。

集合資料操作效率

有哪些底層資料結構?

集合型別的底層資料結構主要有 5 種:

  1. 整數陣列
  2. 雙向連結串列
  3. 雜湊表
  4. 壓縮列表
  5. 跳錶

壓縮列表

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

  1. 查詢第一個或最後一個元素可以根據屬性, O(1)
  2. 其它位置, O(n)

跳錶

跳錶在連結串列的基礎上,增加了多級索引,通過索引位置的幾個跳轉,實現資料的快速定位,如下圖所示

資料結構的時間複雜度

不同操作的複雜度

集合常見操作的複雜度:

  1. 單元素操作是基礎;O(1)
  2. 範圍操作非常耗時;O(n)
  3. 統計操作通常高效;O(1), 比如計算集合的長度, 因為內部記錄統計資訊, 可以直接獲取
  4. 例外情況只有幾個;O(1), 比如操作雙向連結串列的頭/尾

小結

Redis 之所以能快速操作鍵值對,一方面是因為 O(1) 複雜度的雜湊表被廣泛使用,包括 String、Hash 和 Set,它們的操作複雜度基本由雜湊表決定,另一方面,Sorted Set 也採用了 O(logN) 複雜度的跳錶。不過,集合型別的範圍操作,因為要遍歷底層資料結構,複雜度通常是 O(N)。這裡,我的建議是:用其他命令來替代,例如可以用 SCAN 來代替,避免在 Redis 內部產生費時的全集合遍歷操作。