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 內部產生費時的全集合遍歷操作。