再探Redis對象與底層數據結構的關係

大綱:簡述Redis五種對象所使用的的底層數據結構

  • 字符串對象
  • 列表對象
  • 哈希對象
  • 集合對象
  • 有序集合對象

閱讀本文你將收穫什麼:

  • 瞭解Redis五種對象的實現以及優點。
  • 瞭解對象系統設計上的優點。

簡述:
上文我們已經瞭解過了Redis底層的六種數據結構,然而這六種數據結構咱們並不能直接使用,而是基於這些數據結構創建了Redis的對象系統,每種對象都用到了至少一種前文所述的數據結構。
這種以對象來實現方式的好處在於,可以根據對象的類型來判斷對象是否可以執行給定的命令。另一個好處在於,我們可以針對不同的使用場景,爲對象設置不同的數據結構實現,優化使用效率。

一。對象的編碼與類型

圖1·Redis對象與編碼.png
圖1·Redis對象與編碼

圖一表示了Redis與其對象系統,也就是咱們常用的五大類型,以及五大類型底層所有的編碼方式。

對象的編碼:

編碼常量 編碼所對應的底層數據結構
REDIS_ENCODING_INT long類型的整數
REDIS_ENCODING_EMBSTR embstr編碼的簡單動態字符串
REDIS_ENCODING_RAW 簡單動態字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 雙端鏈表
REDIS_ENCODING_ZIPLIST 壓縮列表
REDIS_ENCODING_INTSE 整數集合
REDIS_ENCODING_SKIPLIST 跳躍表和字典

二.字符串對象

字符串對象的編碼可以是int、raw、或者embstr。

  1. 如果一個字符串對象保存的是整數值,且這個整數值可以用long類型來表示,那麼字符串對象會將整數值保存在字符串對象結構的ptr屬性裏面,並且將字符串對象的編碼設置爲int。
    圖2·int編碼的字符串對象.png
    圖2·int編碼的字符串對象

  2. 如果字符串對象保存的是一個字符串值,且長度大於39字節,那麼字符串對象將使用一個簡單動態字符串(SDS)來保存這個值,並將對象的編碼設置爲raw。
    圖3·raw編碼的字符串對象.png
    圖3·raw編碼的字符串對象

  3. 如果保存的字符串值的長度小於39字節,那麼字符串對象將使用embstr編碼的方式來保存這個字符串值。
    圖4·embstr編碼創建的內存塊結構.png
    圖4·embstr編碼創建的內存塊結構

使用embstr保存短字符串值的好處

  • embstr編碼將創建字符串對象所需的內存分配次數從raw編碼的兩次降低爲一次。
  • 釋放embstr編碼的字符串對象只需要調用一次內存釋放函數,而釋放raw編碼的字符串對象需要調用兩次。
  • 因爲embstr編碼的字符串對象所有的數據都保存在一塊連續的內存裏,所以這種編碼的字符串對象比raw編碼的字符串對象能夠更好的利用緩存帶來的優勢。

Redis沒有爲embstr編碼的字符串對象編寫任何相應的修改程序,所以embstr編碼的字符串對象實際上是隻讀的,任何對其修改的命令,程序都會將其編碼從embstr轉換成raw,在執行修改。

三.列表對象

列表對象的編碼可以是ziplist或者linkedlist。

  1. ziplist編碼的列表對象使用壓縮列表作爲底層實現,每個壓縮列表節點(entry)保存了一個列表元素。

圖5·ziplist編碼的number列表對象.png
圖5·ziplist編碼的number列表對象

  1. 另一方面,linkedlist編碼的列表對象使用雙端鏈表作爲底層實現,每個雙端鏈表節點(node)都保存了一個字符串對象,每個字符串對象都保存了一個列表元素。

圖6·linkedlist編碼的列表對象.png
圖6·linkedlist編碼的列表對象

字符串對象是Redis五種類型的對象中唯一一種會被其他四種對象嵌套的對象。

編碼轉換
當列表對象可以同時滿足以下兩個條件時,列表對象使用ziplist編碼:

  • 列表對象保存的所有字符串長度都小於64字節;
  • 列表對象保存的元素數量小於512個。

四.哈希對象

哈希對象的編碼可以是ziplist或者hashtable。

  1. ziplist編碼的哈希對象使用壓縮列表作爲底層實現,每當有新的鍵值對要加入到哈希對象時,程序會先將保存了鍵的壓縮列表節點推入到壓縮列表表尾,然後再將保存了值的壓縮列表節點推入到壓縮列表表表尾,因此:
    • 保存了同一鍵值對的兩個接地那總是緊挨在一起,保存鍵的節點在前,保存值的節點在後;
    • 先添加到哈希對象中的鍵值對會被放在壓縮列表的表頭方向,而後來添加到哈希對象中的鍵值對會被放在壓縮列表的表尾方向。

舉例:
ziplist編碼的哈希對象

圖7·ziplist編碼的哈希對象.png
圖7·ziplist編碼的哈希對象

壓縮列表實現如下:

圖8·哈希對象的壓縮列表底層實現.png
圖8·哈希對象的壓縮列表底層實現

  1. hashtable編碼的哈希對象使用字典作爲底層實現,哈希對象中的每個鍵值對都是用一個字典鍵值對來保存:
    • 字典的每個鍵都是一個字符串對象,對象中保存了鍵值對的鍵。
    • 字典的每個值都是一個字符串對象,對象中保存了鍵值對的值。

圖9·hashtable編碼的哈希對象.png
圖9·hashtable編碼的哈希對象

編碼轉換
當哈希對象可以同時滿足以下兩個條件時,哈希對象使用ziplist編碼:

  • 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小於64字節;
  • 哈希對象保存的鍵值對數量小於512個。

五.集合對象

集合對象的編碼可以是intset或者hashtable。

  1. intset編碼的集合對象使用整數集合作爲底層實現,集合對象包含所有元素都被保存在整數集合裏面。

圖10·intset編碼的集合對象.png
圖10·intset編碼的集合對象

  1. 另一方面,hashtable編碼的集合對象使用字典作爲底層實現,字典的每個鍵都是一個字符串對象,每個字符串對象包含了一個集合元素,而字典的值全部被設置爲NULL。

圖11·hashtable編碼的集合對象.png
圖11·hashtable編碼的集合對象

編碼轉換
當集合對象可以同時滿足以下兩個條件時,對象使用intset編碼:

  • 集合對象保存的所有元素都是整數值;
  • 集合對象保存的元素數量不超過512個。

六.有序集合對象

有序集合的編碼可以是ziplist或者skiplist。

  1. ziplist編碼的有序集合對象使用壓縮列表作爲底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個元素保存分值。
    壓縮列表內的幾何元素按分值從小到大進行排序,分值較小的元素被放置在靠近表頭的位置,而分值較大的元素則被放置在靠近表尾的位置。

圖12·有序集合的壓縮列表.png
圖12·有序集合的壓縮列表

  1. zset結構中的zsl跳躍表按照分值從小到大保存了所有集合元素,每個跳躍表節點都保存了一個集合元素:跳躍表節點的object屬性保存了元素的成員,而跳躍表節點的score屬性則保存了元素的分值。通過這個跳躍表,程序可以對有序集合進行範圍型操作。
    除此之外zset結構中的dict字典爲有序集合創建了一個從成員到分值的映射,字典中的每個鍵值對都保存了一個集合元素:字典的鍵保存了元素的成員,值則保存了元素的分值。通過字典,程序可以以O(1)的複雜度查找給定成員的分值。

圖13·skiplist編碼的有序集合對象.png
圖13·skiplist編碼的有序集合對象

圖14·有序集合元素同時被保存在字典和跳躍表中.png
圖14·有序集合元素同時被保存在字典和跳躍表中

編碼轉換
當有序集合對象可同時滿足以下兩個條件時,對象使用ziplist編碼:

  • 有序集合保存的元素數量小於128個;
  • 有序集合保存的所有元素成員的長度都小於64字節。

總結

以上就是Redis的對象系統如何使用底層基本數據結構來針對不同的場景進行實現。同時,經過分析,可以發現,Redis的對象系統的類型與編碼組合使用以達到多種適用於不同類型, 不同長度數據存儲的巧妙設計。

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