Redis(一):redis基本數據類型與底層存儲結構


最近在整理有關redis的相關知識,對於redis的基本數據類型以及其底層的存儲結構簡要的進行匯總和備注(主要為面試用😂

Redis對外提供的基本數據類型主要為五類,分別是

  • STRING:可以存儲字符串、數字
  • LIST:列表,鏈表的每個節點存儲一個字符串對象
  • HASH:包含鍵值對的無需散列表
  • SET:無序集合,集合中包含的是不重復的集合對象
  • ZSET:有序集合,是有一對一對字符串成員-浮點數分值所構成的有序映射,排序規則由分值大小所決定

以上是我們在使用Redis的時候經常見到的五種數數據結構,這五種數據結構在底層存儲上又有着千絲萬縷的聯系;例如字符串對象作為一個最基本的存儲對象,其在上午五種數據結構中均有應用,那么具體在Redis底層數據結構中是如何構造這五種數據結構的,本文將對底層存儲做深入解析。

文章中所描述的數據結構大都基於2.9版本,如有描述不對還請留言指正,萬分感謝🍻

一、STRING

字符串對象根據保存值的類型、長度不同,可以分為三種存儲結構

  • 如果存儲的是整數值(可以用long表示),則底層通過如下結構進行存儲,其中type代表當前對象為STRING對象,encoding表示當前對象的編碼格式,ptr的屬性保存是真實的值;

舉例:

  • 如果存儲的是字符串且字符串長度超過39字節,則底層通過如下結構進行存儲,其中type代表當前對象為STRING對象,encoding表示當前對象的編碼格式,ptr為指針指向一個SDS(shshdr:簡單動態字符串對象)來保存具體的值;

舉例:

  • 如果存儲的是字符串且字符串長度未超過39字節,則底層通過如下結構進行存儲(需要一塊連續的內存空間),其中type代表當前對象為STRING對象,encoding表示當前對象的編碼格式,ptr為指針指向一個SDS(shshdr:簡單動態字符串對象)來保存具體的值;

舉例:

  • 存儲結構差異
    • embstr需要一塊連續的內存空間,因此其效率上比raw方式要高
    • emstr在內存分配以及內存釋放時只需要一次接口,而raw方式需要兩次(因為存在redisObject和shshdr兩個對象)
    • embstr為只讀對象,任何對embstr編碼對象的修改都會導致對象的編碼格式變為raw
    • int/embstr編碼格式的字符串對象在滿足一定條件后會自動轉為raw編碼格式
  • 字符串對象常用的命令
命令 作用 備注
set 設置key的值 根據值不同底層會采用三種不同的編碼進行存儲
get 獲取字符串對象值 對於int編碼格式有個值拷貝-轉換的過程
append 在現有字符串值后面追加新的值 int/embstr編碼格式對象會先轉換為raw后在執行追加操作
incrbyfloat 對浮點型數值進行加法操作  
incrby 對整數型數值進行加法操作 embstr/raw不能執行此命令
decrby 對整數型數值進行減法操作 embstr/raw不能執行此命令
strlen 返回字符串長度 int編碼格式需要拷貝對象並轉換為raw格式后在執行操作
setrange 在字符串指定索引上的值設置為給定值 int/embstr需要轉換為raw后在執行操作
getrange 返回字符串指定索引的值 int編碼格式需要拷貝對象並轉換為raw格式后在執行操作

 

二、LIST

列表對象根據存儲數據的長度以及存儲數據元素個數的不同,可以分為兩種存儲結構:

  • 如果列表對象保存的所有字符串對象值的長度均未超過64字節且列表對象保存元素數量小於512個的時候,就采用ziplist(壓縮列表)格式存儲

  • 如果列表對象保存的所有字符串對象值的長度有超過64字節或者列表對象保存元素數量大於等於512個的時候,就采用linkedlist(雙端鏈表)格式存儲

  • 存儲結構差異
    • 壓縮列表是有一系列特殊編碼的連續內存塊組成的順序型數據結構,而雙端鏈表則不需要連續的內存塊
    • 壓縮列表的每個節點是由三部分組成(previous_entry_length/encoding/content),其中previous_entry_length是記錄前一個節點的長度,以便程序可以通過任意節點的指針計算出前一個節點的起始位置;而雙端鏈表則必須通過頭尾節點進行遍歷獲取
    • 由於previous_entry_length屬性會隨着前一個節點的字節長度不同而存儲1或者5字節,如果新增的頭結點長度大於254字節,會導致當前頭結點的previous_entry_length(假設當前節點的previous_entry_length為1)的長度無法保存新節點的長度,此時程序會對原頭節點進行內存空間重新分配,最壞的情況是新增的頭結點導致原列表中的所有元素全部重新分配;而雙端鏈表則不會存在該問題;因此在使用列表對象時要考慮連鎖更新的問題;

 

三、HASH

哈希對象根據存儲鍵值對數據長度以及鍵值對數量,可以分為兩種存儲結構:

  • 如果hash對象保存的所有鍵值對的字符串長度小於64直接且鍵值對數量小於512個,采用ziplist(壓縮列表)編碼存儲

key-value總是以成對的方式存在,存儲順序類似於棧,先添加的鍵值對會存在列表的前面,后添加的會在列表的尾部;

  • 如果hash對象保存的鍵值對的字符串長度有超過64字節或者鍵值對數量大於等於512個,將采用hashtable編碼存儲

  • 上述存儲格式會隨着存儲的內容變化進行編碼格式轉變,轉變只能從ziplist轉換為hashtable

 

 

四、SET

集合對象根據存儲數據的長度以及存儲數據元素類型的不同,可以分為兩種存儲結構:

  • 如果列表對象保存的所有元素均是整數型且保存元素數量不超過512個的時候,就采用intset編碼存儲

  • 如果列表對象保存的元素有不是整數型或者保存元素數量超過512個的時候,就采用hashtable編碼存儲

  • 上述存儲格式會隨着存儲的內容變化進行編碼格式轉變,轉變只能從intset轉換為hashtable
  • 在實際使用過程中,需要提前規划好存儲數據內容,盡量不要出現編碼格式轉換

五、ZSET

有序集合對象根據數據元素數量以元素成員長度,可以分為兩種存儲結構:

  • 如果有序集合對象保存的元素數量小於128個且有序結合對象保存的元素成員的長度都小於64字節,就采用ziplist(壓縮列表)編碼存儲

使用壓縮列表的有序結合中每個對象由兩個節點構成,第一個節點保存元素的具體值,第二個節點保存元素的分值;默認情況元素是按照分值從小到大排序;

  • 如果有序集合對象保存的元素數量>=128個或有序結合對象保存的元素成員的長度存在>=64字節,就采用skplist(跳躍表【詳細請參見:算法-跳躍表原理與實現】)和dict編碼存儲

為了兼顧查找與范圍查找,有序集合需要同時使用跳躍表與字典

  1. 字典可以保證O(1)復雜度直接根據指定key查找成員值
  2. 跳躍表可以在O(logN)的復雜度下完成范圍查找,遠遠優於字典的O(NlogN)

在底層存儲上,針對相同的數據對象以及分值,跳躍表與字典會通過指針進行共享,因此不會產生較多的內存浪費

 

六、總結

Redis對外提供的是上述五種數據類型,但是在底層構造這五種數據類型時,底層實際上使用了包括“簡單動態字符串”、“鏈表”、“字典”、“跳躍表”、“整數集合”、“壓縮列表”來構造

根據存儲數據的類型、長度以及數量,不同存儲格式之間可以進行動態轉換,有的存儲結構體現是查詢速度,有的存儲接口體現的空間占用

STRING

INT
embstr
raw
LIST ziplist
linkedlist
HASH ziplist
ht
SET intset
ht
ZSET ziplist
skiplist

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM