最近在整理有關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編碼儲存
為了兼顧查詢與範圍查詢,有序集合需要同時使用跳躍表與字典
- 字典可以保證O(1)複雜度直接根據指定key查詢成員值
- 跳躍表可以在O(logN)的複雜度下完成範圍查詢,遠遠優於字典的O(NlogN)
在底層儲存上,針對相同的資料物件以及分值,跳躍表與字典會通過指標進行共享,因此不會產生較多的記憶體浪費
六、總結
Redis對外提供的是上述五種資料型別,但是在底層構造這五種資料型別時,底層實際上使用了包括“簡單動態字串”、“連結串列”、“字典”、“跳躍表”、“整數集合”、“壓縮列表”來構造
根據儲存資料的型別、長度以及數量,不同儲存格式之間可以進行動態轉換,有的儲存結構體現是查詢速度,有的儲存介面體現的空間佔用
STRING |
INT |
embstr | |
raw | |
LIST | ziplist |
linkedlist | |
HASH | ziplist |
ht | |
SET | intset |
ht | |
ZSET | ziplist |
skiplist |