8.1對象的類型與編碼
Redis中的每個對象都由一個redisObject結構表示,該結構中和保存數(shù)據(jù)有關的三個屬性分別是type屬性、encoding屬性和ptr屬性:

8.1.1 類型
對于Redis數(shù)據(jù)庫保存的鍵值對來說,鍵總是一個字符串對象,而值則可以是字符串對象、列表對象、哈希對象、集合對象和有序集合對象其中的一種。
TYPE命令返回的結果為數(shù)據(jù)庫鍵對應的值對象的類型,而不是鍵對象的類型。
8.1.2 編碼和底層實現(xiàn)
對象的ptr指針指向對象的底層實現(xiàn)數(shù)據(jù)結構,而這些數(shù)據(jù)結構由對象的encoding屬性決定。
encoding屬性記錄了對象所使用的編碼。每種類型的對象都至少使用了兩種不同的編碼。通過encoding屬性來設定對象所使用的編碼,而不是為特定類型的對象關聯(lián)一種固定的編碼,極大地提升了Redis的靈活性和效率。
8.2 字符串對象
字符串對象的編碼可以int、raw或者embstr.
如果一個字符串對象保存的是整數(shù)值,并且這個整數(shù)值可以用long類型來表示,那么字符串對象會將整數(shù)值保存在字符串對象結構的ptr屬性里面,并將字符串對象的編碼設置為int
如果字符串對象保存的是一個字符串值,并且這個字符串值的長度大于32字節(jié),那么字符串對象將使用一個簡單動態(tài)字符串(SDS)來保存這個字符串的值,并將對象的編碼設置為raw
如果字符串對象保存的 是一個字符串值,并且這個字符串值的長度小于等于32字節(jié),那么字符串對象將使用embstr編碼的方式來保存這個字符串值。
embstr編碼是專門用于保存段字符串的一種優(yōu)化編碼方式,它和raw編碼一樣,都使用redisObject結構和sdshdr結構來表示字符串對象,raw編碼會調用兩次內存分配函數(shù)來分別創(chuàng)建redisObject結構和sdshdr結構,而embstr編碼則通過一次內存分配函數(shù)來分配一塊連續(xù)的空間,空間中依次包含redisObject和sdshdr兩個結構。
embstr編碼的字符串對象來保存短字符串值有以下好處:
1.embstr編碼將創(chuàng)建字符串對象所需內存的次數(shù)從raw編碼的兩次降低為1次。
2.釋放embstr編碼的字符串對象只需要調用一次內存釋放函數(shù),而釋放raw編碼的字符串對象需要調用2次內存釋放函數(shù)
3.embstr編碼的字符串對象的所有數(shù)據(jù)都保存在一塊連續(xù)的內存中,能更好地利用緩存帶來的優(yōu)勢。

8.2.1 編碼的轉換
int編碼的字符串對象和embstr編碼的字符串對象在條件滿足的情況下,會被轉化為raw編碼的字符串對象。
embstr編碼的字符串對象實際上是只讀的。當我們對embstr編碼的字符串對象指向任何修改命令時,程序會先將對象的編碼從embstr轉換成raw,然后再執(zhí)行修改命令。
8.2.2 字符串命令的實現(xiàn)

8.3 列表對象
列表對象的編碼可以是ziplist或者linkedlist
ziplist編碼的列表對象使用壓縮列表作為底層實現(xiàn),每個壓縮列表節(jié)(entry)點保存了一個列表元素。
linkedlist編碼的列表對象使用雙端鏈表作為底層實現(xiàn),每個雙端鏈表節(jié)點(node)都保存了一個字符串對象,而每個字符串對象都保存了一個列表元素。
8.3.1 編碼轉換
當列表對象可以同時滿足以下兩個條件時,列表對象使用ziplist編碼:
1.列表對象保存的所有字符串元素的長度都小于64字節(jié)
2.列表對象保存的元素數(shù)量小于512個
不能滿足這兩個條件的列表對象需要使用linkedlist編碼
8.3.2 列表命令的實現(xiàn)

8.4 哈希對象
哈希對象的編碼可以是ziplist或者hashtable
ziplist編碼的哈希對象底層是壓縮列表實現(xiàn)的。保存了同一鍵值對的兩個節(jié)點總是緊挨在一起,保存鍵的節(jié)點在前,值的節(jié)點在后
先添加到哈希對象中的鍵值對會被放在壓縮列表的表頭方向,后被添加的鍵值對會被放到表尾方向
hashtable編碼的哈希對象使用字典作為底層實現(xiàn)
8.4.1 編碼轉換
當哈希表同時滿足以下兩個條件時,哈希對象使用ziplist編碼:
1.哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于64
2.哈希對象保存的鍵值對小于512個
不滿足這兩個條件的哈希對象需要使用hashtable編碼

8.5 集合對象
集合對象的編碼可以是intset或者hashtable
intset編碼的集合對象使用整數(shù)集合作為底層實現(xiàn),集合對象包含的所有對象都被保存在整數(shù)集合里面
8.5.1 編碼的轉換
當集合對象同時滿足以下兩個條件時,集合對象使用intset編碼:
1.集合對象保存的所有元素都是整數(shù)值
2.集合對象保存的鍵值對小于512個
不滿足這兩個條件的對象集合需要使用hashtable進行編碼
8.5.2 集合命令的實現(xiàn)
因為集合鍵的值為集合對象,所以用于集合鍵的所有命令都是針對集合對象來構建的

8.6 有序集合對象
有序集合對象的編碼可以是ziplist或者skiplist
ziplist編碼的壓縮對象使用壓縮列表作為底層實現(xiàn),每個集合元素使用兩個挨在一起的壓縮列表節(jié)點來保存,第一個節(jié)點保存元素的成員,而第二個元素則保存元素的分值。壓縮列表內的集合元素按分值從小到大進行排序,分值較小的元素被放置在靠近表頭的方向,而分值較大的元素被放置在靠近表尾的方向。
skiplist編碼的有序集合對象使用zset結構作為底層實現(xiàn),一個zset結構同時包含一個字典和一個跳躍表:

zset結構同時使用跳躍表和字典來保存有序集合元素,但這兩種數(shù)據(jù)結構都會通過指針來共享相同元素的成員和分值,所以同時使用跳躍表和字典來保存集合元素不會產生任何重復成員或分值
8.6.1 編碼的轉換
當有序集合對象可以同時滿足以下兩個條件時,對象使用ziplist編碼:
1.有序集合保存的元素數(shù)量小于128個
2.有序集合保存的所有元素成員的程度都小于64字節(jié)
不滿足以上兩個條件就使用skiplist
8.6.2 有序集合命令的實現(xiàn)

8.7 類型檢查與命令多態(tài)
Redis中用于操作鍵的命令基本可以分為2類:
1.可以對任何類型的鍵執(zhí)行,比如DEL命令,EXPIRE命令,RENAME命令,TYPE命令,OBJECT命令
2.只能對特定類型的鍵執(zhí)行,比如:
SET、GET APPEND STRLEN命令只能對字符串鍵執(zhí)行
HDEL HSET HGET HLEN等命令只能對哈希鍵執(zhí)行
RPUSH LPOP LINSERT LLEN只能對列表鍵執(zhí)行
SADD SPOP SINTER SCARD只能對集合鍵執(zhí)行
ZADD ZCARD ZRANK ZSCORE只能對有序集合鍵執(zhí)行
8.7.1 類型檢查的實現(xiàn)
類型檢查時通過redisObject中的type屬性來實現(xiàn)的:
1.在執(zhí)行一個類型特定命令之前,服務器會先檢查輸入數(shù)據(jù)庫鍵的值對象是否為執(zhí)行命令所需的類型,如果是的話,服務器就對鍵執(zhí)行指定的命令
2.否則,服務器將拒絕執(zhí)行命令,并向客戶端返回一個類型錯誤
8.7.2 多態(tài)命令的實現(xiàn)
可以將DEL EXPIRE TYPE等命令也稱為多態(tài),是基于類型的多態(tài)——一個命令可以同時處理多鐘不同類型的鍵
LLEN命令多態(tài)是基于編碼的多態(tài)——一個命令可以同時用于多種不同編碼
8.8 內存回收
Redis構建了一個引用計數(shù)技術實現(xiàn)內存回收機制,通過這一機制,程序可以跟蹤對象的引用計數(shù)信息,在適當?shù)臅r候自動釋放對象并進行內存回收
每個對象的引用計數(shù)信息由redisObject結構的refcount屬性記錄

對象的引用計數(shù)信息會隨著對象的使用狀態(tài)而不斷變化:
在創(chuàng)建一個新對象時,引用計數(shù)的值會被初始化為1
當對象被一個新程序使用時,它的引用計數(shù)值會被增一
當對象不再被一個程序使用時,他的引用計數(shù)值會被減一
當對象的引用計數(shù)值變?yōu)?時,對象所占用的內存會被釋放
8.9 對象共享
對象的引用計數(shù)屬性海帶有對象共享的作用
在Redis中,讓多個鍵共享同一個值對象需要執(zhí)行以下兩個步驟:
1.將數(shù)據(jù)庫鍵的值指針指向一個現(xiàn)有的值對象
2.將被共享的值對象的引用計數(shù)增一
共享機制對節(jié)約內存非常有幫助,數(shù)據(jù)庫中保存的相同值對象越多,對象共享機制就能節(jié)約越多內存
目前來說,redis會在初始化服務器時,創(chuàng)建一萬個字符串對象,這些對象包含了從0到9999的所有整數(shù)值,當服務器需要用到值為0-9999的字符串對象時,服務器就會使用這些共享對象,而不是創(chuàng)建新對象
8.10 對象的空轉時長
redisObject結構包含的最后一個屬性為lru屬性,該屬性記錄了對象最后一次被命令程序訪問的時間

OBJECT IDLETIME命令可以打印出給定鍵的空轉時長,這一空轉時長就是通過將當前時間減去鍵的值對象的lru時間計算得出的。
鍵的空轉時長還有一項作用:如果服務器打開了maxmemory選項,并且服務器用于回收內存的算法為volatile-lru或者allkeys-lru,那么當服務器占用的內存數(shù)超過了maxmemory選項所設置的上限值時,空轉時長較高的那部分鍵會優(yōu)先被服務器釋放,從而回收內存。