大綱:簡述Redis五種對象所使用的的底層數據結構
- 字符串對象
- 列表對象
- 哈希對象
- 集合對象
- 有序集合對象
閱讀本文你將收穫什麼:
- 瞭解Redis五種對象的實現以及優點。
- 瞭解對象系統設計上的優點。
簡述:
上文我們已經瞭解過了Redis底層的六種數據結構,然而這六種數據結構咱們並不能直接使用,而是基於這些數據結構創建了Redis的對象系統,每種對象都用到了至少一種前文所述的數據結構。
這種以對象來實現方式的好處在於,可以根據對象的類型來判斷對象是否可以執行給定的命令。另一個好處在於,我們可以針對不同的使用場景,爲對象設置不同的數據結構實現,優化使用效率。
一。對象的編碼與類型
圖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。
-
如果一個字符串對象保存的是整數值,且這個整數值可以用long類型來表示,那麼字符串對象會將整數值保存在字符串對象結構的ptr屬性裏面,並且將字符串對象的編碼設置爲int。
圖2·int編碼的字符串對象 -
如果字符串對象保存的是一個字符串值,且長度大於39字節,那麼字符串對象將使用一個簡單動態字符串(SDS)來保存這個值,並將對象的編碼設置爲raw。
圖3·raw編碼的字符串對象 -
如果保存的字符串值的長度小於39字節,那麼字符串對象將使用embstr編碼的方式來保存這個字符串值。
圖4·embstr編碼創建的內存塊結構
使用embstr保存短字符串值的好處
- embstr編碼將創建字符串對象所需的內存分配次數從raw編碼的兩次降低爲一次。
- 釋放embstr編碼的字符串對象只需要調用一次內存釋放函數,而釋放raw編碼的字符串對象需要調用兩次。
- 因爲embstr編碼的字符串對象所有的數據都保存在一塊連續的內存裏,所以這種編碼的字符串對象比raw編碼的字符串對象能夠更好的利用緩存帶來的優勢。
Redis沒有爲embstr編碼的字符串對象編寫任何相應的修改程序,所以embstr編碼的字符串對象實際上是隻讀的,任何對其修改的命令,程序都會將其編碼從embstr轉換成raw,在執行修改。
三.列表對象
列表對象的編碼可以是ziplist或者linkedlist。
- ziplist編碼的列表對象使用壓縮列表作爲底層實現,每個壓縮列表節點(entry)保存了一個列表元素。
圖5·ziplist編碼的number列表對象
- 另一方面,linkedlist編碼的列表對象使用雙端鏈表作爲底層實現,每個雙端鏈表節點(node)都保存了一個字符串對象,每個字符串對象都保存了一個列表元素。
圖6·linkedlist編碼的列表對象
字符串對象是Redis五種類型的對象中唯一一種會被其他四種對象嵌套的對象。
編碼轉換
當列表對象可以同時滿足以下兩個條件時,列表對象使用ziplist編碼:
- 列表對象保存的所有字符串長度都小於64字節;
- 列表對象保存的元素數量小於512個。
四.哈希對象
哈希對象的編碼可以是ziplist或者hashtable。
- ziplist編碼的哈希對象使用壓縮列表作爲底層實現,每當有新的鍵值對要加入到哈希對象時,程序會先將保存了鍵的壓縮列表節點推入到壓縮列表表尾,然後再將保存了值的壓縮列表節點推入到壓縮列表表表尾,因此:
- 保存了同一鍵值對的兩個接地那總是緊挨在一起,保存鍵的節點在前,保存值的節點在後;
- 先添加到哈希對象中的鍵值對會被放在壓縮列表的表頭方向,而後來添加到哈希對象中的鍵值對會被放在壓縮列表的表尾方向。
舉例:
ziplist編碼的哈希對象
圖7·ziplist編碼的哈希對象
壓縮列表實現如下:
圖8·哈希對象的壓縮列表底層實現
- hashtable編碼的哈希對象使用字典作爲底層實現,哈希對象中的每個鍵值對都是用一個字典鍵值對來保存:
- 字典的每個鍵都是一個字符串對象,對象中保存了鍵值對的鍵。
- 字典的每個值都是一個字符串對象,對象中保存了鍵值對的值。
圖9·hashtable編碼的哈希對象
編碼轉換
當哈希對象可以同時滿足以下兩個條件時,哈希對象使用ziplist編碼:
- 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小於64字節;
- 哈希對象保存的鍵值對數量小於512個。
五.集合對象
集合對象的編碼可以是intset或者hashtable。
- intset編碼的集合對象使用整數集合作爲底層實現,集合對象包含所有元素都被保存在整數集合裏面。
圖10·intset編碼的集合對象
- 另一方面,hashtable編碼的集合對象使用字典作爲底層實現,字典的每個鍵都是一個字符串對象,每個字符串對象包含了一個集合元素,而字典的值全部被設置爲NULL。
圖11·hashtable編碼的集合對象
編碼轉換
當集合對象可以同時滿足以下兩個條件時,對象使用intset編碼:
- 集合對象保存的所有元素都是整數值;
- 集合對象保存的元素數量不超過512個。
六.有序集合對象
有序集合的編碼可以是ziplist或者skiplist。
- ziplist編碼的有序集合對象使用壓縮列表作爲底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個元素保存分值。
壓縮列表內的幾何元素按分值從小到大進行排序,分值較小的元素被放置在靠近表頭的位置,而分值較大的元素則被放置在靠近表尾的位置。
圖12·有序集合的壓縮列表
- zset結構中的zsl跳躍表按照分值從小到大保存了所有集合元素,每個跳躍表節點都保存了一個集合元素:跳躍表節點的object屬性保存了元素的成員,而跳躍表節點的score屬性則保存了元素的分值。通過這個跳躍表,程序可以對有序集合進行範圍型操作。
除此之外zset結構中的dict字典爲有序集合創建了一個從成員到分值的映射,字典中的每個鍵值對都保存了一個集合元素:字典的鍵保存了元素的成員,值則保存了元素的分值。通過字典,程序可以以O(1)的複雜度查找給定成員的分值。
圖13·skiplist編碼的有序集合對象
圖14·有序集合元素同時被保存在字典和跳躍表中
編碼轉換
當有序集合對象可同時滿足以下兩個條件時,對象使用ziplist編碼:
- 有序集合保存的元素數量小於128個;
- 有序集合保存的所有元素成員的長度都小於64字節。
總結
以上就是Redis的對象系統如何使用底層基本數據結構來針對不同的場景進行實現。同時,經過分析,可以發現,Redis的對象系統的類型與編碼組合使用以達到多種適用於不同類型, 不同長度數據存儲的巧妙設計。