Redis 的底層數據結構(對象)

目前爲止,咱們介紹了 redis 中很是典型的五種數據結構,從 SDS 到 壓縮列表,這都是 redis 最底層、最經常使用的數據結構,相信你也掌握的不錯。java

但 redis 實際存儲鍵值對的時候,是基於對象這個基本單位的,而且每每一個對象下面對對應不一樣的底層數據結構實現以便於在不一樣的場景下切換底層實現提高效率。例如列表對象在元素很少狀況話會使用壓縮列表來實現以壓縮內存,而在元素比較多的時候常規的雙端鏈表進行實現。git

下面咱們就具體來看看 redis 中都有哪些對象,底層又對應哪些可供選擇的數據結構。程序員

1、Redis 對象結構定義

redis 爲每一個對象定義爲以下數據結構:github

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; 
    int refcount;
    void *ptr;
} robj;
複製代碼

type 記錄的是當前的對象類型,有如下幾種類型:redis

#define OBJ_STRING 0 /*字符串對象*/

#define OBJ_LIST 1 /*列表對象*/

#define OBJ_SET 2 /*集合對象*/

#define OBJ_ZSET 3 /*有序集合對象*/

#define OBJ_HASH 4 /*哈希對象*/
複製代碼

encoding 記錄的是當前對象使用的哪一種底層數據結構實現的,有如下類型可供選擇:算法

#define OBJ_ENCODING_RAW 0 /* SDS 字符串 */

#define OBJ_ENCODING_INT 1 /* 整數 */

#define OBJ_ENCODING_HT 2 /* 字典結構 */

#define OBJ_ENCODING_ZIPMAP 3 /* 壓縮map,已經廢棄 */

#define OBJ_ENCODING_LINKEDLIST 4 /* LinkedList 雙端鏈表,廢棄了 */

#define OBJ_ENCODING_ZIPLIST 5 /* 壓縮列表 */

#define OBJ_ENCODING_INTSET 6 /* 整數集合 */

#define OBJ_ENCODING_SKIPLIST 7 /* 跳躍表 */

#define OBJ_ENCODING_EMBSTR 8 /* 短字符串 */

#define OBJ_ENCODING_QUICKLIST 9 /* 壓縮鏈表和雙向鏈表組成的快速列表 */
複製代碼

8 和 9 咱們遇到時在介紹,這裏暫時不作介紹。數據庫

lru 記錄的是上一個當前對象實例被訪問的時間,它用做計算對象空轉時長,空轉時長過大的對象會被 redis 優先釋放內存。編程

refcount 記錄的是對象的引用計數,引用計數算法是不少編程語言中管理對象是否應該被銷燬的依據,和它相似的典型的 Java 中可達性分析算法,都是用於計數當前對象是否依然被使用,以便釋放內存。緩存

ptr 指針指向的是實際實現當前對象的數據結構首地址。bash

以上就是 redisObject 數據結構的基本解釋,下面咱們看具體的對象分別會在什麼狀況下切換不一樣的底層實現。

2、字符串對象

字符串對象有三種 encoding 值,也就是隻有這三種狀況,redis 纔會使用字符串對象存儲數據。

  1. 字符串(raw):普通的字符串
  2. 整數(int):long 類型的整數值
  3. 短字符串(embstr ):短字符串

若是斷定使用 raw 編碼,那麼 redis 的 ptr 指針將會指向一個 SDS 結構,若是肯定使用 int 編碼,那麼會將 redisObject 中 ptr 類型由 void* 變成 long,繼而分配 robj 內存。

當字符串的長度小於 39 個字節時,會採用 embstr 這種編碼,embstr 其實也是使用 SDS 進行存儲,區別於 raw 編碼的是,後者會將 robj 和 ptr 指向的 SDS 分配在連續的內存塊,惟一的好處是分配和釋放內存都只須要一次操做便可完成,再一個是由於數據相鄰,有可能一次加載 robj 的時候,CPU 將後面的 embstr 也加載進緩存,等到訪問的時候就能夠直接從緩存中訪問。

可是,咱們看一個例子:

image

hello 本來是以 int 編碼存儲的,可是咱們執行 append 命令添加了字符串以後,它變成了 raw 編碼。

這實際上是 redis 的一種編碼換換,當 hello 再也不適合使用 int 編碼繼續存儲的時候,會進行一個編碼轉換。

3、列表對象

列表對象有兩種編碼,壓縮列表 ziplist 和 linkedlist。咱們以前說過壓縮列表的推薦應用場景,少許整數或字符串的時候能夠用壓縮列表來節省內存空間,而大數據量的節點則推薦使用普通的雙端鏈表進行實現。

可是實際上,redis 的較新版本已經使用一種叫 quicklist 的快速列表整合 ziplist 和 linkedlist 做爲列表對象的實現了。它將全部的節點分段拆分,每一份又使用壓縮列表進行壓縮,不一樣段之間使用雙向指針鏈接。

image

4、集合對象

集合對象也有兩種編碼,整數集合 intset 和 字典 hashtable。默認狀況下,當集合中有且僅有整型數據,且不超過 512 個,那麼 redis 會使用整數集合 intset 進行集合存儲,其他狀況 redis 則構建字典進行集合數據存儲。

image

順便給你們複習下 intset 的無重複性、順序性的特性,重複的元素是插入不進去的,由於插入以前會經過二分查找查找是否存在該元素,若是存在則拒絕插入操做。

固然了,若是集合中元素個數超過 512,那麼 redis 就轉而使用字典結構進行數據存儲,具體實例就再也不演示了。

5、有序集合對象

有序集合對象一樣使用兩種編碼 ziplist 和 skiplist,可能你又見到壓縮列表的身影了,足以見得,壓縮列表是一個很是優秀的數據結構。

一樣,當有序集合中包含少許元素的時候,redis 會優先使用壓縮列表進行存儲,反之選擇跳躍表。

image

sadd 命令的標準語法是:

ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN
複製代碼

每個元素都會對應一個分值,skiplist 自己的實現就須要這個分值進行元素的存儲排序,有的時候有序集合會使用壓縮列表進行實現,那麼也須要這個分值來有序的壓縮元素,這也是壓縮列表頁能夠實現有序集合的緣由。

這裏補充一下,雖說 redis 的有序集合是跳錶實現的,這句話不錯,但有失偏駁。

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;
複製代碼

準確來講,redis 中的有序集合是由咱們以前介紹過的字典加上跳錶(組合起來就是zset)實現的,字典中保存的數據和分數 score 的映射關係,每次插入數據會從字典中查詢,若是已經存在了,就再也不插入,有序集合中是不容許重複數據。

6、哈希對象

哈希對象的編碼能夠是 ziplist 或者 hashtable,沒什麼特殊的,再也不贅述。

image

以上,咱們總結了 redis 中五大對象結構,以及他們可選的底層實現數據結構,相信你也理解的不錯,這將很是有助於咱們後面的學習。

下節開始,咱們向 redis 數據庫邁進~


關注公衆不迷路,一個愛分享的程序員。 公衆號回覆「1024」加做者微信一塊兒探討學習! 每篇文章用到的全部案例代碼素材都會上傳我我的 github github.com/SingleYam/o… 歡迎來踩!

YangAM 公衆號
相關文章
相關標籤/搜索