五類對象就是咱們經常使用的string、list、set、zset、hashjava
咱們平時主要是經過操做對象的api來操做redis,而不是經過它的調用它底層數據結構來完成(外觀模式)。但咱們還須要瞭解其底層,只有這樣才能寫最優化高效的代碼。git
typedef struct redisObject {
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 指向底層實現數據結構的指針
void *ptr;
// ...
} robj;
複製代碼
記錄對象類型。github
咱們平時用的命令type <key>
,其實就是返回這個字段的屬性。redis
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有多少中類型呢?看下面這個表:算法
對象 | type 字段 | TYPE命令的輸出 |
---|---|---|
字符串對象 | REDIS_STRING | "string" |
列表對象 | REDIS_LIST | |
哈希對象 | REDIS_HASH | |
集合對象 | REDIS_SET | |
有序集合對象 | REDIS_ZSET |
記錄對象使用的編碼(數據結構),Reids中稱數據結構爲encoding。api
咱們能夠這樣查看咱們redis對象中的encoding:bash
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding list
"quicklist"
...
複製代碼
既然它是標明該redisObject
是使用的什麼數據結構,那確定也有個對應的表:數據結構
類型 | 編碼 | 對象 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整數值實現的字符串對象。 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用 embstr 編碼的簡單動態字符串實現的字符串對象。 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用簡單動態字符串實現的字符串對象。 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用壓縮列表實現的列表對象。 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用雙端鏈表實現的列表對象。 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用壓縮列表實現的哈希對象。 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典實現的哈希對象。 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整數集合實現的集合對象。 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典實現的集合對象。 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用壓縮列表實現的有序集合對象。 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳躍表和字典實現的有序集合對象。 |
咱們能夠看到,Redis對對象的底層encoding分的很細,String類型就有三個,其它四個對象都分別有兩種不一樣的底層數據結構的實現。他們有一規律,就是用ziplist
、intset
、embstr
來實現少許的數據,數據量一旦龐大,就會升級到skiplist
、raw
、linkedlist
、ht
來實現,後面我會仔細講解。app
字符串編碼有三個:int、raw、embstr。jvm
當string對象的值所有是數字,就會使用int編碼。
127.0.0.1:6379> set number 123455
OK
127.0.0.1:6379> object encoding number
"int"
複製代碼
字符串或浮點數長度小於等於39字節,就會使用embstr編碼方式來存儲,embstr存儲內存通常很小,因此redis一次性分配且內存連續(效率高)。
127.0.0.1:6379> set shortStr "suwe suwe suwe"
OK
127.0.0.1:6379> object encoding shortStr
"embstr"
複製代碼
當一個字符串或浮點數長度大於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"
複製代碼
前面說過,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"
複製代碼
列表對象編碼能夠是:ziplist或linkedlist。
ziplist
壓縮列表不知道你們還記得不,就是zlbytes zltail zllen entry1 entry2 ..end
結構,entry節點
裏有pre-length、encoding、content
屬性,忘記的能夠返回去看下。
linkedlist
,相似雙向鏈表,也是上一章的知識。
ziplist->linkedlist
條件:列表對象的全部字符串元素的長度大於等於64字節 & 列表元素數大於等於512. 反之,小於64和小於512會使用ziplist而不是用linkedlist。
這個閾值是能夠修改的,修改選項:
list-max-ziplist-value
和list-max-ziplist-entriess
哈希對象的編碼有:ziplist和hashtable
ziplist->hashtable
條件:哈希對象全部鍵和值字符串長度大於等於64字節 & 鍵值對數量大於等於512
這個閾值也是能夠修改的,修改選項:
hash-max-ziplist-value
和hash-max-ziplist-entriess
集合對象的編碼有:intset和hashtable
intset->hashtable
條件:元素不都是整數 & 元素數大於等於512
有序集合用到的編碼:ziplist和skiplist
你們可能很好奇阿,ziplist的entry中只有屬性content能夠存放數據,集合也是key-value
形式,那怎麼存儲呢?
第一個節點保存key、第二個節點保存value 以此類推...
ziplist->skiplist
條件:有序集合元素數 >= 128 & 含有元素的長度 >= 64
這個閾值也是能夠修改的,修改選項:
zset-max-ziplist-value
和zset-max-ziplist-entriess
爲何要說內存回收呢,由於redisObject有一個字段:
typedef struct redisObject {
// ...
// 引用計數
int refcount;
// ...
} robj;
複製代碼
redis的垃圾回收採用引用計數法(和jvm同樣),底層採用一個變量對對象的使用行爲進行計數。
節約內存
成本過高。
驗證整數相等只須要O(1)的時間複雜度,而驗證字符串要O(n).
最後,redisObject還有一個字段,記錄了對象最後一次被訪問的時間:
typedef struct redisObject {
// ...
unsigned lru:22;
// ...
} robj;
複製代碼
由於這個字段記錄對象最後一次被訪問的時間,因此它能夠用來查看該對象多久未使用,即:用當前時間-lru
127.0.0.1:6379> object idletime hello
(integer) 5110
複製代碼
它還關係到redis的熱點數據實現,若是咱們選擇lr算法,當內存超出閾值後會對空閒時長較高的對象進行釋放,回收內存。
關注個人公衆號,隨時閱讀個人所有文章。
想看往期文章, 請點擊個人GitHub地址: github.com/fantj2016/j…