前言
在上一篇關(guān)于redis的文章中们豌,我們分析了redis用到的主要的數(shù)據(jù)結(jié)構(gòu),但是redis并沒有直接使用這些數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)KV形式的數(shù)據(jù)庫,而是基于這些數(shù)據(jù)結(jié)構(gòu)又封裝了一些對象崖疤。常用的基礎(chǔ)對象包括字符串嗜闻、列表蜕依、哈希意荤、集合和有序集合5種,而每種對象底層的實現(xiàn)就是上一篇提到的數(shù)據(jù)結(jié)構(gòu)中的一種或多種辆沦。參考《redis設(shè)計與實現(xiàn)》权烧,redis這么設(shè)計有以下幾點的好處:
- redis執(zhí)行命令前可以根據(jù)對象的具體類型來判斷是否可以執(zhí)行,比如hset作用于字符串對象就會出錯檐束;
- 同一個對象底層可以有多種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)辫秧,可以針對不同的場景做動態(tài)替換,優(yōu)化效率厢塘;
- redis的對象系統(tǒng)實現(xiàn)了引用計數(shù)方式的內(nèi)存回收機制茶没,對象不再使用時占用的內(nèi)存會自動釋放;
- redis的對象系統(tǒng)實現(xiàn)了基于引用計數(shù)的對象共享機制晚碾,某些情況下抓半,多個數(shù)據(jù)庫鍵可以共享同一個對象;
- redis對象帶有訪問記錄信息格嘁,可以計算空轉(zhuǎn)時長笛求,方便內(nèi)存淘汰。
對象類型和編碼
redis中的每個對象都由redisObject結(jié)構(gòu)表示:
typedef struct redisObject {
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 對象最后一次被訪問的時間
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用計數(shù)
int refcount;
// 指向?qū)嶋H值的指針
void *ptr;
} robj;
type代表對象的類型糕簿,取值可以是以下幾種探入,redis支持使用TYPE命令來查看數(shù)據(jù)庫鍵對應(yīng)的值對象的類型
對象 | 對象 type 屬性的值 |
TYPE 命令的輸出 |
---|---|---|
字符串對象 | REDIS_STRING |
"string" |
列表對象 | REDIS_LIST |
"list" |
哈希對象 | REDIS_HASH |
"hash" |
集合對象 | REDIS_SET |
"set" |
有序集合對象 | REDIS_ZSET |
"zset" |
encoding屬性記錄了對象使用的編碼,其實就是底層用了哪個數(shù)據(jù)結(jié)構(gòu)懂诗,可以使用OBJECT ENCODING 命令來查看數(shù)據(jù)庫鍵的值對象的編碼蜂嗽。
類型 | 編碼 | 對象 | OBJECT ENCODING 命令輸出 |
---|---|---|---|
REDIS_STRING |
REDIS_ENCODING_INT |
使用整數(shù)值實現(xiàn)的字符串對象。 | "int" |
REDIS_STRING |
REDIS_ENCODING_EMBSTR |
使用 embstr 編碼的簡單動態(tài)字符串實現(xiàn)的字符串對象殃恒。 |
"embstr" |
REDIS_STRING |
REDIS_ENCODING_RAW |
使用簡單動態(tài)字符串實現(xiàn)的字符串對象植旧。 | "raw" |
REDIS_LIST |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的列表對象。 | "ziplist" |
REDIS_LIST |
REDIS_ENCODING_LINKEDLIST |
使用雙端鏈表實現(xiàn)的列表對象离唐。 | "linkedlist" |
REDIS_HASH |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的哈希對象病附。 | "ziplist" |
REDIS_HASH |
REDIS_ENCODING_HT |
使用字典實現(xiàn)的哈希對象。 | "hashtable" |
REDIS_SET |
REDIS_ENCODING_INTSET |
使用整數(shù)集合實現(xiàn)的集合對象亥鬓。 | "intset" |
REDIS_SET |
REDIS_ENCODING_HT |
使用字典實現(xiàn)的集合對象完沪。 | "hashtable" |
REDIS_ZSET |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的有序集合對象。 | "ziplist" |
REDIS_ZSET |
REDIS_ENCODING_SKIPLIST |
使用跳躍表和字典實現(xiàn)的有序集合對象嵌戈。 | "skiplist" |
字符串對象
從上一個表格中可以看到覆积,redis中字符串的表示可以是int、embstr后者raw熟呛。具體以哪種方式存儲是需要根據(jù)不同的條件來決定的技健。
值 | 編碼 |
---|---|
可以用 long 類型保存的整數(shù)。 |
int |
可以用 long double 類型保存的浮點數(shù)惰拱。 |
embstr 或者 raw
|
字符串值雌贱, 或者因為長度太大而沒辦法用 long 類型表示的整數(shù)啊送, 又或者因為長度太大而沒辦法用 long double 類型表示的浮點數(shù)。 |
embstr 或者 raw
|
- long類型的整數(shù)用int類型
- 字符串小于等于32字節(jié)用embstr類型
- 字符串大于32字節(jié)用raw類型
- Long轉(zhuǎn)為字符串的時候會從int轉(zhuǎn)為raw類型(如對int類型做APPEND操作)
- embstr類型是只讀的欣孤,如果對字符串變更馋没,會轉(zhuǎn)為raw類型(如APPEND操作)
附一個《redis設(shè)計與實現(xiàn)》中的字符串命令實現(xiàn)的表格:
命令 |
int 編碼的實現(xiàn)方法 |
embstr 編碼的實現(xiàn)方法 |
raw 編碼的實現(xiàn)方法 |
---|---|---|---|
SET | 使用 int 編碼保存值。 |
使用 embstr 編碼保存值降传。 |
使用 raw 編碼保存值篷朵。 |
GET | 拷貝對象所保存的整數(shù)值, 將這個拷貝轉(zhuǎn)換成字符串值婆排, 然后向客戶端返回這個字符串值声旺。 | 直接向客戶端返回字符串值。 | 直接向客戶端返回字符串值段只。 |
APPEND | 將對象轉(zhuǎn)換成 raw 編碼腮猖, 然后按 raw 編碼的方式執(zhí)行此操作。 |
將對象轉(zhuǎn)換成 raw 編碼赞枕, 然后按 raw 編碼的方式執(zhí)行此操作澈缺。 |
調(diào)用 sdscatlen 函數(shù), 將給定字符串追加到現(xiàn)有字符串的末尾炕婶。 |
INCRBYFLOAT | 取出整數(shù)值并將其轉(zhuǎn)換成 longdouble 類型的浮點數(shù)姐赡, 對這個浮點數(shù)進行加法計算, 然后將得出的浮點數(shù)結(jié)果保存起來柠掂。 |
取出字符串值并嘗試將其轉(zhuǎn)換成long double 類型的浮點數(shù)项滑, 對這個浮點數(shù)進行加法計算, 然后將得出的浮點數(shù)結(jié)果保存起來涯贞。 如果字符串值不能被轉(zhuǎn)換成浮點數(shù)枪狂, 那么向客戶端返回一個錯誤。 |
取出字符串值并嘗試將其轉(zhuǎn)換成 longdouble 類型的浮點數(shù)肩狂, 對這個浮點數(shù)進行加法計算, 然后將得出的浮點數(shù)結(jié)果保存起來姥饰。 如果字符串值不能被轉(zhuǎn)換成浮點數(shù)傻谁, 那么向客戶端返回一個錯誤。 |
INCRBY | 對整數(shù)值進行加法計算列粪, 得出的計算結(jié)果會作為整數(shù)被保存起來审磁。 |
embstr 編碼不能執(zhí)行此命令, 向客戶端返回一個錯誤岂座。 |
raw 編碼不能執(zhí)行此命令态蒂, 向客戶端返回一個錯誤。 |
DECRBY | 對整數(shù)值進行減法計算费什, 得出的計算結(jié)果會作為整數(shù)被保存起來钾恢。 |
embstr 編碼不能執(zhí)行此命令, 向客戶端返回一個錯誤。 |
raw 編碼不能執(zhí)行此命令瘩蚪, 向客戶端返回一個錯誤泉懦。 |
STRLEN | 拷貝對象所保存的整數(shù)值, 將這個拷貝轉(zhuǎn)換成字符串值疹瘦, 計算并返回這個字符串值的長度崩哩。 | 調(diào)用 sdslen 函數(shù), 返回字符串的長度言沐。 |
調(diào)用 sdslen 函數(shù)邓嘹, 返回字符串的長度。 |
SETRANGE | 將對象轉(zhuǎn)換成 raw 編碼险胰, 然后按 raw 編碼的方式執(zhí)行此命令汹押。 |
將對象轉(zhuǎn)換成 raw 編碼, 然后按 raw 編碼的方式執(zhí)行此命令鸯乃。 |
將字符串特定索引上的值設(shè)置為給定的字符鲸阻。 |
GETRANGE | 拷貝對象所保存的整數(shù)值, 將這個拷貝轉(zhuǎn)換成字符串值缨睡, 然后取出并返回字符串指定索引上的字符鸟悴。 | 直接取出并返回字符串指定索引上的字符。 | 直接取出并返回字符串指定索引上的字符奖年。 |
列表對象
列表對象的編碼可以是壓縮列表或者雙端鏈表细诸,也就是ziplist或者linkedlist。壓縮列表的存儲結(jié)構(gòu)可以參見上一篇介紹redis數(shù)據(jù)結(jié)構(gòu)的文章陋守,而雙端鏈表底層每個節(jié)點都保存了一個字符串對象震贵,字符串對象也是唯一一種會被其他對象嵌套引用的對象。
當(dāng)列表對象可以同時滿足以下兩個條件時水评,列表對象使用 ziplist 編碼猩系,具體的長度和數(shù)量的限制值也可以通過配置文件進行配置:
- 列表對象保存的所有字符串元素的長度都小于 64 字節(jié);
- 列表對象保存的元素數(shù)量小于 512 個中燥;
同樣附上列表命令的實現(xiàn)表格:
命令 |
ziplist 編碼的實現(xiàn)方法 |
linkedlist 編碼的實現(xiàn)方法 |
---|---|---|
LPUSH | 調(diào)用 ziplistPush 函數(shù)寇甸, 將新元素推入到壓縮列表的表頭。 |
調(diào)用 listAddNodeHead 函數(shù)疗涉, 將新元素推入到雙端鏈表的表頭拿霉。 |
RPUSH | 調(diào)用 ziplistPush 函數(shù), 將新元素推入到壓縮列表的表尾咱扣。 |
調(diào)用 listAddNodeTail 函數(shù)绽淘, 將新元素推入到雙端鏈表的表尾。 |
LPOP | 調(diào)用 ziplistIndex 函數(shù)定位壓縮列表的表頭節(jié)點闹伪, 在向用戶返回節(jié)點所保存的元素之后沪铭, 調(diào)用 ziplistDelete 函數(shù)刪除表頭節(jié)點壮池。 |
調(diào)用 listFirst 函數(shù)定位雙端鏈表的表頭節(jié)點, 在向用戶返回節(jié)點所保存的元素之后伦意, 調(diào)用 listDelNode 函數(shù)刪除表頭節(jié)點火窒。 |
RPOP | 調(diào)用 ziplistIndex 函數(shù)定位壓縮列表的表尾節(jié)點, 在向用戶返回節(jié)點所保存的元素之后驮肉, 調(diào)用 ziplistDelete 函數(shù)刪除表尾節(jié)點熏矿。 |
調(diào)用 listLast 函數(shù)定位雙端鏈表的表尾節(jié)點, 在向用戶返回節(jié)點所保存的元素之后离钝, 調(diào)用 listDelNode 函數(shù)刪除表尾節(jié)點票编。 |
LINDEX | 調(diào)用 ziplistIndex 函數(shù)定位壓縮列表中的指定節(jié)點, 然后返回節(jié)點所保存的元素卵渴。 |
調(diào)用 listIndex 函數(shù)定位雙端鏈表中的指定節(jié)點慧域, 然后返回節(jié)點所保存的元素。 |
LLEN | 調(diào)用 ziplistLen 函數(shù)返回壓縮列表的長度浪读。 |
調(diào)用 listLength 函數(shù)返回雙端鏈表的長度昔榴。 |
LINSERT | 插入新節(jié)點到壓縮列表的表頭或者表尾時, 使用 ziplistPush 函數(shù)碘橘; 插入新節(jié)點到壓縮列表的其他位置時互订, 使用 ziplistInsert 函數(shù)。 |
調(diào)用 listInsertNode 函數(shù)痘拆, 將新節(jié)點插入到雙端鏈表的指定位置仰禽。 |
LREM | 遍歷壓縮列表節(jié)點, 并調(diào)用 ziplistDelete 函數(shù)刪除包含了給定元素的節(jié)點纺蛆。 |
遍歷雙端鏈表節(jié)點吐葵, 并調(diào)用 listDelNode 函數(shù)刪除包含了給定元素的節(jié)點。 |
LTRIM | 調(diào)用 ziplistDeleteRange 函數(shù)桥氏, 刪除壓縮列表中所有不在指定索引范圍內(nèi)的節(jié)點温峭。 |
遍歷雙端鏈表節(jié)點, 并調(diào)用 listDelNode 函數(shù)刪除鏈表中所有不在指定索引范圍內(nèi)的節(jié)點字支。 |
LSET | 調(diào)用 ziplistDelete 函數(shù)凤藏, 先刪除壓縮列表指定索引上的現(xiàn)有節(jié)點, 然后調(diào)用 ziplistInsert 函數(shù)祥款, 將一個包含給定元素的新節(jié)點插入到相同索引上面清笨。 |
調(diào)用 listIndex 函數(shù)月杉, 定位到雙端鏈表指定索引上的節(jié)點刃跛, 然后通過賦值操作更新節(jié)點的值。 |
哈希對象
哈希對象的編碼可以是ziplist或者hashtable苛萎。
- 對于使用壓縮列表來實現(xiàn)的哈希對象桨昙,每次增加新的鍵值對會先將代表鍵的壓縮列表節(jié)點添加到表尾检号,然后再將代表值的壓縮列表節(jié)點添加到表尾,所以鍵值總是連在一起蛙酪。
- 對于使用字典實現(xiàn)的哈希對象齐苛,不管鍵和值都是一個字符串對象。
當(dāng)哈希對象可以同時滿足以下兩個條件時桂塞, 哈希對象使用 ziplist 編碼凹蜂,同樣,兩個限制值是可以通過配置文件進行修改的:
- 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于 64 字節(jié)阁危;
- 哈希對象保存的鍵值對數(shù)量小于 512 個玛痊;
附上哈希命令的實現(xiàn)表格:
命令 |
ziplist 編碼實現(xiàn)方法 |
hashtable 編碼的實現(xiàn)方法 |
---|---|---|
HSET | 首先調(diào)用 ziplistPush 函數(shù), 將鍵推入到壓縮列表的表尾狂打, 然后再次調(diào)用 ziplistPush 函數(shù)擂煞, 將值推入到壓縮列表的表尾。 |
調(diào)用 dictAdd 函數(shù)趴乡, 將新節(jié)點添加到字典里面对省。 |
HGET | 首先調(diào)用 ziplistFind 函數(shù), 在壓縮列表中查找指定鍵所對應(yīng)的節(jié)點晾捏, 然后調(diào)用 ziplistNext 函數(shù)蒿涎, 將指針移動到鍵節(jié)點旁邊的值節(jié)點, 最后返回值節(jié)點粟瞬。 |
調(diào)用 dictFind 函數(shù)同仆, 在字典中查找給定鍵, 然后調(diào)用 dictGetVal 函數(shù)裙品, 返回該鍵所對應(yīng)的值俗批。 |
HEXISTS | 調(diào)用 ziplistFind 函數(shù), 在壓縮列表中查找指定鍵所對應(yīng)的節(jié)點市怎, 如果找到的話說明鍵值對存在岁忘, 沒找到的話就說明鍵值對不存在。 |
調(diào)用 dictFind 函數(shù)区匠, 在字典中查找給定鍵干像, 如果找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在驰弄。 |
HDEL | 調(diào)用 ziplistFind 函數(shù)麻汰, 在壓縮列表中查找指定鍵所對應(yīng)的節(jié)點, 然后將相應(yīng)的鍵節(jié)點戚篙、 以及鍵節(jié)點旁邊的值節(jié)點都刪除掉五鲫。 |
調(diào)用 dictDelete 函數(shù), 將指定鍵所對應(yīng)的鍵值對從字典中刪除掉岔擂。 |
HLEN | 調(diào)用 ziplistLen 函數(shù)位喂, 取得壓縮列表包含節(jié)點的總數(shù)量浪耘, 將這個數(shù)量除以 2 , 得出的結(jié)果就是壓縮列表保存的鍵值對的數(shù)量塑崖。 |
調(diào)用 dictSize 函數(shù)七冲, 返回字典包含的鍵值對數(shù)量, 這個數(shù)量就是哈希對象包含的鍵值對數(shù)量规婆。 |
HGETALL | 遍歷整個壓縮列表澜躺, 用 ziplistGet 函數(shù)返回所有鍵和值(都是節(jié)點)。 |
遍歷整個字典抒蚜, 用 dictGetKey 函數(shù)返回字典的鍵苗踪, 用 dictGetVal 函數(shù)返回字典的值。 |
集合對象
集合對象的編碼可以是intset或者hashtable削锰,也就是整數(shù)集合或者字典通铲。intset實現(xiàn)的集合所有元素都是整數(shù),而hashtable實現(xiàn)的集合元素則每個鍵都是字符串對象器贩,每個值都是NULL颅夺,類似于Java中HashSet的實現(xiàn)。
當(dāng)集合對象可以同時滿足以下兩個條件時蛹稍,對象使用 intset 編碼吧黄,數(shù)量上限同樣可配置:
- 集合對象保存的所有元素都是整數(shù)值;
- 集合對象保存的元素數(shù)量不超過 512 個唆姐;
集合命令的實現(xiàn)表格:
命令 |
intset 編碼的實現(xiàn)方法 |
hashtable 編碼的實現(xiàn)方法 |
---|---|---|
SADD | 調(diào)用 intsetAdd 函數(shù)拗慨, 將所有新元素添加到整數(shù)集合里面。 |
調(diào)用 dictAdd 奉芦, 以新元素為鍵赵抢, NULL 為值, 將鍵值對添加到字典里面声功。 |
SCARD | 調(diào)用 intsetLen 函數(shù)烦却, 返回整數(shù)集合所包含的元素數(shù)量, 這個數(shù)量就是集合對象所包含的元素數(shù)量先巴。 |
調(diào)用 dictSize 函數(shù)其爵, 返回字典所包含的鍵值對數(shù)量, 這個數(shù)量就是集合對象所包含的元素數(shù)量伸蚯。 |
SISMEMBER | 調(diào)用 intsetFind 函數(shù)摩渺, 在整數(shù)集合中查找給定的元素, 如果找到了說明元素存在于集合剂邮, 沒找到則說明元素不存在于集合摇幻。 |
調(diào)用 dictFind 函數(shù), 在字典的鍵中查找給定的元素, 如果找到了說明元素存在于集合囚企, 沒找到則說明元素不存在于集合。 |
SMEMBERS | 遍歷整個整數(shù)集合瑞眼, 使用 intsetGet 函數(shù)返回集合元素龙宏。 |
遍歷整個字典, 使用 dictGetKey 函數(shù)返回字典的鍵作為集合元素伤疙。 |
SRANDMEMBER | 調(diào)用 intsetRandom 函數(shù)银酗, 從整數(shù)集合中隨機返回一個元素。 |
調(diào)用 dictGetRandomKey 函數(shù)徒像, 從字典中隨機返回一個字典鍵黍特。 |
SPOP | 調(diào)用 intsetRandom 函數(shù), 從整數(shù)集合中隨機取出一個元素锯蛀, 在將這個隨機元素返回給客戶端之后灭衷, 調(diào)用 intsetRemove 函數(shù), 將隨機元素從整數(shù)集合中刪除掉旁涤。 |
調(diào)用 dictGetRandomKey 函數(shù)翔曲, 從字典中隨機取出一個字典鍵, 在將這個隨機字典鍵的值返回給客戶端之后劈愚, 調(diào)用 dictDelete 函數(shù)瞳遍, 從字典中刪除隨機字典鍵所對應(yīng)的鍵值對。 |
SREM | 調(diào)用 intsetRemove 函數(shù)菌羽, 從整數(shù)集合中刪除所有給定的元素掠械。 |
調(diào)用 dictDelete 函數(shù), 從字典中刪除所有鍵為給定元素的鍵值對注祖。 |
有序集合對象
有序集合的編碼可以是 ziplist 或者 skiplist猾蒂,即跳躍表或者壓縮列表。跟哈希對象使用壓縮列表實現(xiàn)類似是晨,每個有序集合元素使用相連的兩個壓縮列表節(jié)點分別表示成員和分值婚夫,分值按從小到大的順序排序。
而使用跳躍表實現(xiàn)的有序集合底層使用了zset結(jié)構(gòu)署鸡,zset包含了一個跳躍表和一個字典:
typedef struct zset {
zskiplist *zsl;
dict *dict;
} zset;
zset 結(jié)構(gòu)中的 zsl 跳躍表按分值從小到大保存了所有集合元素案糙, 每個跳躍表節(jié)點都保存了一個集合元素: 跳躍表節(jié)點的 object 屬性保存了元素的成員, 而跳躍表節(jié)點的 score 屬性則保存了元素的分值靴庆。
zset 結(jié)構(gòu)中的 dict 字典為有序集合創(chuàng)建了一個從成員到分值的映射时捌, 字典中的每個鍵值對都保存了一個集合元素: 字典的鍵保存了元素的成員, 而字典的值則保存了元素的分值炉抒。
同時使用兩個數(shù)據(jù)結(jié)構(gòu)綜合了兩種結(jié)構(gòu)的優(yōu)點奢讨,通過跳躍表能夠方便的執(zhí)行范圍查詢,而通過字典能夠方便的進行元素分數(shù)查詢焰薄。
有序集合每個元素的成員都是一個字符串對象拿诸, 而每個元素的分值都是一個 double 類型的浮點數(shù)扒袖。跳躍表和字典底層通過指針會共享這些對象,所以不會浪費額外的內(nèi)存亩码。
當(dāng)有序集合對象可以同時滿足以下兩個條件時季率,對象使用 ziplist 編碼,可通過配置文件設(shè)置限制值:
- 有序集合保存的元素數(shù)量小于 128 個描沟;
- 有序集合保存的所有元素成員的長度都小于 64 字節(jié)飒泻;
有序集合命令的實現(xiàn)集合:
命令 |
ziplist 編碼的實現(xiàn)方法 |
zset 編碼的實現(xiàn)方法 |
---|---|---|
ZADD | 調(diào)用 ziplistInsert 函數(shù), 將成員和分值作為兩個節(jié)點分別插入到壓縮列表吏廉。 |
先調(diào)用 zslInsert 函數(shù)泞遗, 將新元素添加到跳躍表席覆, 然后調(diào)用 dictAdd 函數(shù)畦戒, 將新元素關(guān)聯(lián)到字典垃环。 |
ZCARD | 調(diào)用 ziplistLen 函數(shù), 獲得壓縮列表包含節(jié)點的數(shù)量霹肝, 將這個數(shù)量除以 2 得出集合元素的數(shù)量。 |
訪問跳躍表數(shù)據(jù)結(jié)構(gòu)的 length 屬性系枪, 直接返回集合元素的數(shù)量。 |
ZCOUNT | 遍歷壓縮列表宏榕, 統(tǒng)計分值在給定范圍內(nèi)的節(jié)點的數(shù)量。 | 遍歷跳躍表抚芦, 統(tǒng)計分值在給定范圍內(nèi)的節(jié)點的數(shù)量倍谜。 |
ZRANGE | 從表頭向表尾遍歷壓縮列表, 返回給定索引范圍內(nèi)的所有元素叉抡。 | 從表頭向表尾遍歷跳躍表尔崔, 返回給定索引范圍內(nèi)的所有元素。 |
ZREVRANGE | 從表尾向表頭遍歷壓縮列表褥民, 返回給定索引范圍內(nèi)的所有元素季春。 | 從表尾向表頭遍歷跳躍表, 返回給定索引范圍內(nèi)的所有元素消返。 |
ZRANK | 從表頭向表尾遍歷壓縮列表载弄, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點的數(shù)量撵颊, 當(dāng)找到給定成員之后侦锯, 途經(jīng)節(jié)點的數(shù)量就是該成員所對應(yīng)元素的排名。 | 從表頭向表尾遍歷跳躍表秦驯, 查找給定的成員尺碰, 沿途記錄經(jīng)過節(jié)點的數(shù)量, 當(dāng)找到給定成員之后, 途經(jīng)節(jié)點的數(shù)量就是該成員所對應(yīng)元素的排名亲桥。 |
ZREVRANK | 從表尾向表頭遍歷壓縮列表洛心, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點的數(shù)量题篷, 當(dāng)找到給定成員之后词身, 途經(jīng)節(jié)點的數(shù)量就是該成員所對應(yīng)元素的排名。 | 從表尾向表頭遍歷跳躍表番枚, 查找給定的成員法严, 沿途記錄經(jīng)過節(jié)點的數(shù)量, 當(dāng)找到給定成員之后葫笼, 途經(jīng)節(jié)點的數(shù)量就是該成員所對應(yīng)元素的排名深啤。 |
ZREM | 遍歷壓縮列表, 刪除所有包含給定成員的節(jié)點路星, 以及被刪除成員節(jié)點旁邊的分值節(jié)點溯街。 | 遍歷跳躍表, 刪除所有包含了給定成員的跳躍表節(jié)點洋丐。 并在字典中解除被刪除元素的成員和分值的關(guān)聯(lián)呈昔。 |
ZSCORE | 遍歷壓縮列表, 查找包含了給定成員的節(jié)點友绝, 然后取出成員節(jié)點旁邊的分值節(jié)點保存的元素分值堤尾。 | 直接從字典中取出給定成員的分值。 |
類型檢查和命令多態(tài)
redis中的命令分為兩種:對所有類型的鍵都可以執(zhí)行以及只能對特定的鍵執(zhí)行迁客,而只能對特性鍵執(zhí)行的命令需要進行鍵的類型檢查哀峻。
回顧一下redisObject機構(gòu),其中有個屬性是type哲泊,redis在執(zhí)行類型特定命令前就是通過這個type屬性來判斷能夠繼續(xù)執(zhí)行的剩蟀。
對于某種redis對象來說,底層會有多種實現(xiàn)切威,比如列表對象就可以有壓縮列表和雙端鏈表兩種實現(xiàn)育特,而對于同一個命令,不同的實現(xiàn)應(yīng)該都能夠響應(yīng)先朦,針對不同的數(shù)據(jù)結(jié)構(gòu)來做不同的處理缰冤,這就是命令的多態(tài)。
內(nèi)存回收
C語言不像Java擁有虛擬機和垃圾回收器喳魏,所以redis自己實現(xiàn)了一個基于引用計數(shù)的內(nèi)存回收機制棉浸。每個redisObject里都有一個refcount的屬性,這個屬性值會隨著對象的使用狀態(tài)不同而不斷變化:
- 在創(chuàng)建一個新對象時刺彩, 引用計數(shù)的值會被初始化為 1 迷郑;
- 當(dāng)對象被一個新程序使用時枝恋, 它的引用計數(shù)值會被增一;
- 當(dāng)對象不再被一個程序使用時嗡害, 它的引用計數(shù)值會被減一焚碌;
- 當(dāng)對象的引用計數(shù)值變?yōu)?0 時, 對象所占用的內(nèi)存會被釋放霸妹。
對象共享
對于不同的鍵引用了相同的值對象十电,redis實現(xiàn)了對象共享機制,通過將指針指向相同的對象來實現(xiàn)內(nèi)存的高效利用叹螟。同時refcount屬性記錄了引用的數(shù)量鹃骂。
默認redis會在初始化服務(wù)器的時候就創(chuàng)建一萬個字符串對象,包含從0到9999的所有整數(shù)值罢绽。
redis實現(xiàn)對象共享只針對于整數(shù)型的字符串對象畏线,原因如下:
- 如果共享對象是保存整數(shù)值的字符串對象, 那么驗證操作的復(fù)雜度為 O(1) 有缆;
- 如果共享對象是保存字符串值的字符串對象象踊, 那么驗證操作的復(fù)雜度為 O(N) 温亲;
- 如果共享對象是包含了多個值(或者對象的)對象棚壁, 比如列表對象或者哈希對象, 那么驗證操作的復(fù)雜度將會是 O(N^2) 栈虚。
因此袖外, 盡管共享更復(fù)雜的對象可以節(jié)約更多的內(nèi)存, 但受到 CPU 時間的限制魂务, Redis 只對包含整數(shù)值的字符串對象進行共享曼验。
對象的空轉(zhuǎn)時長
對象的空轉(zhuǎn)時長可以通過redisObject中的lru屬性實現(xiàn),OBJECT IDLETIME 命令可以通過將當(dāng)前時間減去lru的值來計算對象的空轉(zhuǎn)時長粘姜。并且使用 OBJECT IDLETIME 命令并不會更新對象的lru的值鬓照。
對象的lru還被用于實現(xiàn)redis的特定內(nèi)存淘汰策略,這里就不詳述了孤紧。