上一章我們講了Redis的底層數據結構,不了解的人可能會有疑問:這個和平時用的五大對象有啥關系呢?這一章我們就主要解釋他們所建立的聯系。
看這個文章之前,如果對ziplist、skiplist、intset等數據結構不熟悉的話,建議先回顧一下上一章節:面試官:你看過Redis數據結構底層實現嗎?
0. 五類對象分別是什么
五類對象就是我們常用的string、list、set、zset、hash
1. 為什么要有對象
我們平時主要是通過操作對象的api來操作redis,而不是通過它的調用它底層數據結構來完成(外觀模式)。但我們還需要了解其底層,只有這樣才能寫最優化高效的代碼。
-
跟java一樣,對象使開發更方便簡潔,降低開發門檻。開發者不需要了解其復雜的底層API,直接調用高層接口即可實現開發。
-
Redis根據對象類型來判斷命令是否違法,如果你set key value1 value2就報錯。
-
對象下可以包含多種數據結構,使數據存儲更加多態化。(下面主講)
-
Reids基於對象做了垃圾回收(引用計數法)。
-
對象帶有更豐富的屬性,來幫助redis實現更高級的功能。(比如對象的閑置時間)。
2. Redis對象(RedisObject)源碼分析
typedef struct redisObject { // 類型 unsigned type:4; // 編碼 unsigned encoding:4; // 指向底層實現數據結構的指針 void *ptr; // ... } robj;
type字段
記錄對象類型。
我們平時用的命令type <key>
,其實就是返回這個字段的屬性。
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> type hello string 127.0.0.1:6379> rpush list 1 2 3 (integer) 3 127.0.0.1:6379> type list list ...
那type有多少中類型呢?看下面這個表:
encoding字段
記錄對象使用的編碼(數據結構),Reids中稱數據結構為encoding。
我們可以這樣查看我們redis對象中的encoding:
127.0.0.1:6379> object encoding hello "embstr" 127.0.0.1:6379> object encoding list "quicklist" ...
既然它是標明該redisObject
是使用的什么數據結構,那肯定也有個對應的表:
我們可以看到,Redis對對象的底層encoding分的很細,String類型就有三個,其它四個對象都分別有兩種不同的底層數據結構的實現。他們有一規律,就是用ziplist
、intset
、embstr
來實現少量的數據,數據量一旦龐大,就會升級到skiplist
、raw
、linkedlist
、ht
來實現,后面我會仔細講解。
3. 分別分析各個對象的底層編碼實現(數據結構)
3.1 字符串(string)
字符串編碼有三個:int、raw、embstr。
3.1.1 int
當string對象的值全部是數字,就會使用int編碼。
127.0.0.1:6379> set number 123455 OK 127.0.0.1:6379> object encoding number "int"
3.1.2 embstr
字符串或浮點數長度小於等於39字節,就會使用embstr編碼方式來存儲,embstr存儲內存一般很小,所以redis一次性分配且內存連續(效率高)。
127.0.0.1:6379> set shortStr "suwe suwe suwe" OK 127.0.0.1:6379> object encoding shortStr "embstr"
3.1.2 raw
當一個字符串或浮點數長度大於39字節,就使用SDS來保存,編碼為raw,由於不確定值的字節大小,所以鍵和值各分配各的,所以就分配兩次內存(回收也是兩次),同理它一定不是內存連續的。
127.0.0.1:6379> set longStr "hello everyone, we dont need to sleep around to go aheard! do you think?" OK 127.0.0.1:6379> object encoding longStr "raw"
3.1.3 編碼轉換
前面說過,Redis會自動對編碼進行轉換來適應和優化數據的存儲。
int->raw
條件:數字對象進行append字母,就會發生轉換。
127.0.0.1:6379> object encoding number "int" 127.0.0.1:6379> append number " is a lucky number" (integer) 24 127.0.0.1:6379> object encoding number "raw"
embstr->raw
條件:對embstr進行修改,redis會先將其轉換成raw,然后才進行修改。所以embstr實際上是只讀性質的。
127.0.0.1:6379> object encoding shortStr "embstr" 127.0.0.1:6379> append shortStr "(hhh" (integer) 18 127.0.0.1:6379> object encoding shortStr "raw"
3.2 列表(list)
列表對象編碼可以是:ziplist或linkedlist。
-
ziplist
壓縮列表不知道大家還記得不,就是zlbytes zltail zllen entry1 entry2 ..end
結構,entry節點
里有pre-length、encoding、content
屬性,忘記的可以返回去看下。 -
linkedlist
,類似雙向鏈表,也是上一章的知識。
3.2.1 編碼轉換
ziplist->linkedlist
條件:列表對象的所有字符串元素的長度大於等於64字節 & 列表元素數大於等於512. 反之,小於64和小於512會使用ziplist而不是用linkedlist。
這個閾值是可以修改的,修改選項:
list-max-ziplist-value
和list-max-ziplist-entriess
3.3 哈希(hash)
哈希對象的編碼有:ziplist和hashtable
3.3.1 編碼轉換
ziplist->hashtable
條件:哈希對象所有鍵和值字符串長度大於等於64字節 & 鍵值對數量大於等於512
這個閾值也是可以修改的,修改選項:
hash-max-ziplist-value
和hash-max-ziplist-entriess
3.4. 集合(set)
集合對象的編碼有:intset和hashtable
3.4.1 intset
-
集合對象所有元素都是整數
-
集合對象元素數不超過512個
3.4.2 編碼轉換
intset->hashtable
條件:元素不都是整數 & 元素數大於等於512
3.5. 有序集合(zset)
有序集合用到的編碼:ziplist和skiplist
大家可能很好奇阿,ziplist的entry中只有屬性content可以存放數據,集合也是key-value
形式,那怎么存儲呢?
第一個節點保存key、第二個節點保存value 以此類推...
3.5.1 為什么要用這兩個編碼
-
如果只用ziplist來實現,無法做到元素的排序,不支持范圍查找,能做到元素的快速查找。
-
如果只用skiplist來實現,無法做到快速查找,但能做到元素排序、范圍操作。
3.5.2 編碼轉換
ziplist->skiplist
條件:有序集合元素數 >= 128 & 含有元素的長度 >= 64
這個閾值也是可以修改的,修改選項:
zset-max-ziplist-value
和zset-max-ziplist-entriess
4. 垃圾回收
為什么要說內存回收呢,因為redisObject有一個字段:
typedef struct redisObject { // ... // 引用計數 int refcount; // ... } robj;
redis的垃圾回收采用引用計數法(和jvm一樣),底層采用一個變量對對象的使用行為進行計數。
-
初始化為1
-
對象被引用,+1
-
對象引用消除,-1
-
計數器==0, 回收對象
5. 對象共享
5.1 對象共享的體現
-
redis中,值是整數值且相等的兩個對象,redis會將該對象進行共享,且引用計數+1
-
redis啟動會自動生成0-9999的整數值放到內存中來共享。
5.2 為什么要對象共享
節約內存
5.3 為什么不對字符串進行共享
成本太高。
驗證整數相等只需要O(1)的時間復雜度,而驗證字符串要O(n).
6. 對象的空閑時長
最后,redisObject還有一個字段,記錄了對象最后一次被訪問的時間:
typedef struct redisObject { // ... unsigned lru:22; // ... } robj;
因為這個字段記錄對象最后一次被訪問的時間,所以它可以用來查看該對象多久未使用,即:用當前時間-lru
127.0.0.1:6379> object idletime hello
(integer) 5110
它還關系到redis的熱點數據實現,如果我們選擇lr算法,當內存超出閾值后會對空閑時長較高的對象進行釋放,回收內存。
參考文獻:
-
《Redis設計與實現》黃健宏著
-
http://redisbook.com/index.html