相信不少人應該都知道 Redis 有五種數據類型:字符串、列表、哈希、集合和有序集合。但這五種數據類型是什麼含義?Redis 的數據又是怎樣存儲的?今天咱們一塊兒來認識下 Redis 這五種數據結構的含義及其底層實現。數據庫
首先要明確的是,Redis 並無直接使用這五種數據結構來實現鍵值對數據庫,而是基於這些數據結構建立了一套對象系統,咱們常說的數據類型,準確來講,是 Redis 對象系統的類型。緩存
對於 Redis 而言,全部鍵值對的存儲,都是將數據存儲在對象結構中。所不一樣的是,鍵老是一個字符串對象,值能夠是任意類型的對象。
對象源碼結構以下:服務器
typedef struct redisObject { unsigned type:4; // 對象類型 unsigned encoding:4; // 對象編碼 unsigned lru:LRU_BITS; // LRU int refcount; // 引用統計 void *ptr; // 指向底層實現數據結構的指針 } robj;
對象有五種數據類型,就是咱們上面提過的:數據結構
結合咱們上面提到的鍵值對存儲類型的差異,能夠了解到,咱們常說的「一個列表鍵或一個哈希鍵」,本質上指的是:一個 key 對應的 value 是列表對象或哈希對象。函數
對於 type 字段,咱們可使用 TYPE
命令來查看指定 key 對應 value 值的對象類型。
優化
按道理講,已經有了 type,爲何還要搞個編碼呢?ui
想一想看,經過 encoding 屬性,咱們是否是使用不一樣編碼的對象?這種使用方式能夠根據不一樣的使用場景來爲一個對象設置不一樣的編碼,從而優化在某一場景下的效率,極大的提高了 Redis 的靈活性和效率。編碼
舉個栗子,在列表對象包含的元素比較少時,Redis 使用壓縮列表做爲列表對象的底層實現:指針
後面介紹完編碼類型後,咱們會詳細認識不一樣類型對應的各個編碼方式。
encoding 屬性有如下取值:
對象的編碼類型能夠由 OBJECT ENCODING
命令獲取。
OBJECT ENCODING
命令對應源碼以下:
# src/object.c char *strEncoding(int encoding) { switch(encoding) { case OBJ_ENCODING_RAW: return "raw"; case OBJ_ENCODING_INT: return "int"; case OBJ_ENCODING_HT: return "hashtable"; case OBJ_ENCODING_QUICKLIST: return "quicklist"; case OBJ_ENCODING_ZIPLIST: return "ziplist"; case OBJ_ENCODING_INTSET: return "intset"; case OBJ_ENCODING_SKIPLIST: return "skiplist"; case OBJ_ENCODING_EMBSTR: return "embstr"; default: return "unknown"; } }
OBJECT ENCODING
命令輸出值與 encoding 屬性取值對應關係以下:
| 對象使用的底層數據結構 | 編碼常量 | OBJECT ENCODING 輸出 |
| :-: | :-: | :-: |
| 簡單動態字符串 |OBJ_ENCODING_RAW |"raw" |
| 整數 |OBJ_ENCODING_INT |"int" |
| embstr 編碼的簡單動態字符串 |OBJ_ENCODING_EMBSTR |"embstr" |
| 字典 |OBJ_ENCODING_HT |"hashtable" |
| 壓縮列表 |OBJ_ENCODING_ZIPLIST |"ziplist" |
| 快速列表 |OBJ_ENCODING_QUICKLIST |"quicklist" |
| 整數集合 |OBJ_ENCODING_INTSET |"intset" |
| 跳躍表 |OBJ_ENCODING_SKIPLIST |"skiplist" |
總結來看,以下圖:
十一種不一樣編碼的對象分別是:
接下來,咱們將對上述十一種對象一一介紹。以後再一一認識對象編碼。
字符串對象的可選編碼分別是:int、raw 或者 embstr。
若是一個字符串對象保存的是整數值,而且這個整數值能夠用 long 類型表示,那麼字符串對象會將整數值保存在字符串對象結構的 ptr 屬性中,並將字符串對象的編碼設置爲 int。
咱們執行如下 SET 命令,服務器將建立一個以下圖所示的 int 編碼的字符串對象做爲 num 鍵的值:
# redis-cli 127.0.0.1:6380> set num 12345 OK 127.0.0.1:6380> OBJECT ENCODING num "int"
若是字符串對象保存的是一個字符串值,而且這個字符串值的長度大於 44 字節(根據版本的不一樣,這個值會有差別。詳見 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那麼字符串對象將使用**簡單動態字符串(SDS)來保存這個字符串值,並將對象的編碼設置爲 raw。
咱們執行下面的 SET 命令,服務器將建立一個圖 7 所示的 raw 編碼的字符串對象做爲 k1 鍵的值(45 字節):
127.0.0.1:7379> set story 'k01234567890123456789012345678901234567890123' OK 127.0.0.1:7379> OBJECT ENCODING k4 "raw"
若是字符串保存的是一個字符串值,而且這個字符串值的長度小於等於 44 字節(根據版本的不一樣,這個值會有差別。詳見 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那麼字符串對象將使用 embstr 編碼的方式來保存這個字符串。
embstr 編碼是專門用於保存段字符串的一種優化編碼方式,這種編碼和 raw 編碼同樣,都使用 redisObject 和 sdshdr 結構來表示字符串對象。但和 raw 編碼的字符串對象不一樣的是:
相對應的,釋放內存時,embstr 編碼的對象也只需調用一次內存釋放函數。
所以,使用 embstr 編碼的字符串對象來保存短字符串值有如下好處:
如下命令建立了一個 embstr 編碼的字符串對象做爲 msg 鍵的值,值對象結構如圖 8。
127.0.0.1:6380> SET msg hello OK 127.0.0.1:6380> OBJECT ENCODING msg "embstr"
Redis 中,long double 類型的浮點數也是做爲字符串值來保存的。
咱們要保存一個浮點數到字符串對象中,程序會先將這個浮點數轉換成字符串值,而後再保存轉換所得的字符串值。
執行如下代碼,將建立一個包含 3.14 的字符串表示 "3.14" 的字符串對象:
127.0.0.1:6380> SET pi 3.14 OK 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
在有須要的時候,程序會將保存在字符串對象裏的字符串值轉換成浮點數值,執行某些操做,而後將所得的浮點數值轉換回字符串值,繼續保存在字符串對象中。
好比,咱們對 pi 鍵執行如下操做:
127.0.0.1:6380> INCRBYFLOAT pi 2.0 "5.14" 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
執行 INCRBYFLOAT
命令過程當中,實際上就會出現字符串與浮點數值互相轉換的狀況。
int 編碼的字符串對象和 embstr 編碼的字符串對象在知足某些條件的狀況下,會被轉換爲 raw 編碼的字符串對象。
對於 int 編碼的字符串對象來講,若是咱們在執行命令後,使得這個對象保存的再也不是整數值,而是一個字符串,那麼字符串對象就會從 int 變爲 raw。好比 APPEND
命令等。
另外,對於 embstr 編碼的字符串,因爲 Redis 沒有爲其編寫任何相應的修改程序,因此 embstr 編碼的字符串對象其實是隻讀的。當咱們對 embstr 編碼的字符串對象執行任何修改命令時,程序都會先將對象的編碼從 embstr 轉換成 raw。也就是說,embstr 編碼的字符串一旦修改,必定會轉換成 raw 編碼的字符串對象。
對於字符串對象各個編碼的狀況,總結以下:
| 值 | 編碼|
| :-- | :-- |
| 能夠用 long 表示的整數值 | int |
| 能夠用 long double 保存的浮點數 | raw 或 embstr |
| 不能夠用 long 或 long double 表示的整數或小數值 | raw 或 embstr |
| 大於 44 字節的字符串 | raw |
| 小於或等於 44 字節的字符串 | embstr |
列表對象的可選編碼分別是:quicklist(3.2 版本前是 ziplist 和 linkedlist)。
3.2 版本引入了 quicklist 編碼,此編碼結合了 ziplist 和 linkedlist,使用雙向鏈表的形式,在每一個節點上存儲一個 ziplist,而每一個 ziplist 又能夠存儲多個鍵值對。也就是說,quicklist 每一個節點上存儲的不是一個數據,而是一片數據。
執行如下命令,服務器將會建立一個列表對象,quicklist 結構如圖 8 所示:
127.0.0.1:7379> RPUSH animal 'dog' 'cat' 'pig' (integer) 3 (5.12s) 127.0.0.1:7379> OBJECT ENCODING animal "quicklist"