《Redis 设计与实现》读书笔记-Redis 对象
一、Redis 對(duì)象
1.1 Redis 對(duì)象簡(jiǎn)介
Redis 使用對(duì)象來(lái)表示數(shù)據(jù)庫(kù)中鍵和值,當(dāng)我們?cè)跀?shù)據(jù)庫(kù)中存儲(chǔ)一個(gè)鍵值對(duì)時(shí),至少會(huì)創(chuàng)建兩個(gè)對(duì)象,一個(gè)對(duì)象用于存儲(chǔ)鍵值對(duì)的鍵,另一個(gè)對(duì)象用于存儲(chǔ)鍵值對(duì)的值。
Redis 中的每一個(gè)對(duì)象都由一個(gè) redisObject 結(jié)構(gòu)表示,該結(jié)構(gòu)有三個(gè)屬性和保存的數(shù)據(jù)有關(guān),分別是 type 屬性、encoding 屬性和 ptr 屬性。
typedef struct redisObject {// 類型unsigned type:4;// 編碼unsigned encoding:4;// 指向底層數(shù)據(jù)實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針void *ptr; } robj;1.2 對(duì)象類型
redisObject 對(duì)象的 type 屬性記錄了對(duì)象的類型,Redis 中的鍵總是一個(gè)字符串對(duì)象,值對(duì)象可以是字符串對(duì)象、哈希對(duì)象、集合對(duì)象等。
我們可以通過(guò)使用 TYPE key 命令查看數(shù)據(jù)庫(kù)鍵對(duì)應(yīng)的值對(duì)象的類型。
1.3 編碼與底層實(shí)現(xiàn)
encoding 屬性記錄了對(duì)象所使用的編碼,也就是這個(gè)對(duì)象底層實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)。關(guān)于編碼可以使用 OBJECT ENCODING key 命令查看。
二、五種數(shù)據(jù)類型對(duì)象與編碼
2.1 字符串對(duì)象
要注意的是 long、double 浮點(diǎn)數(shù)在 Redis 中也是作為字符串值來(lái)保存的。
(1)字符串對(duì)象編碼
字符串對(duì)象的編碼有 int 、 raw 或者 embstr 三種。
- int:保存的是整數(shù)值
- raw:保存的是字符串,并且字符串的長(zhǎng)度小于等于 39 個(gè)字節(jié)
- embstr:保存的是字符串,并且字符串的長(zhǎng)度大于等于 39 個(gè)字節(jié)
embstr 編碼的好處:
- embstr 編碼將創(chuàng)建字符串對(duì)象所需的內(nèi)存分配次數(shù)從 raw 編碼的兩次降低為一次。
- 釋放 embstr 編碼的字符串對(duì)象只需要調(diào)用一次內(nèi)存釋放函數(shù), 而釋放 raw 編碼的字符串對(duì)象需要調(diào)用兩次內(nèi)存釋放函數(shù)。
- 因?yàn)?embstr 編碼的字符串對(duì)象的所有數(shù)據(jù)都保存在一塊連續(xù)的內(nèi)存里面,所以這種編碼的字符串對(duì)象比起 raw 編碼的字符串對(duì)象能夠更好地利用緩存帶來(lái)的優(yōu)勢(shì)。
(2)編碼轉(zhuǎn)換
int 編碼的字符串對(duì)象和 embstr 編碼的字符串對(duì)象在一定條件下, 會(huì)被轉(zhuǎn)換為 raw 編碼的字符串對(duì)象,如下。
- int 編碼的字符串對(duì)象通過(guò)某些操作,保存的不再是單純的整數(shù)值
- 對(duì) embstr 編碼的字符串對(duì)象執(zhí)行任何修改命令時(shí), 程序會(huì)先將對(duì)象的編碼從 embstr 轉(zhuǎn)換成 raw , 然后再執(zhí)行修改命令
下面是一個(gè)例子,供參考:
127.0.0.1:6379> SET num-str 100 OK 127.0.0.1:6379> OBJECT ENCODING num-str "int" # 追加元素內(nèi)容 127.0.0.1:6379> APPEND num-str "hei~" (integer) 7 127.0.0.1:6379> OBJECT ENCODING num-str "raw"2.2 列表對(duì)象
(1)列表對(duì)象編碼
Redis 3.2 以前的版本中列表的的編碼類型有 ziplist 或 linkedlist。
ziplist 編碼的列表對(duì)象使用壓縮列表作為底層實(shí)現(xiàn), 每個(gè)壓縮列表節(jié)點(diǎn)(entry)保存了一個(gè)列表元素。
linkedlist 編碼的列表對(duì)象使用雙端鏈表作為底層實(shí)現(xiàn), 每個(gè)雙端鏈表節(jié)點(diǎn)(node)都保存了一個(gè)字符串對(duì)象, 而每個(gè)字符串對(duì)象都保存了一個(gè)列表元素。
在 Redis 3.2 以上的版本中列表對(duì)象的編碼替換為 quicklist,它的底層實(shí)現(xiàn)是一個(gè)雙向鏈表,而且是一個(gè)ziplist 數(shù)據(jù)結(jié)構(gòu)的雙向鏈表。關(guān)于這種數(shù)據(jù)結(jié)構(gòu)的詳細(xì)信息就不介紹了,以后有機(jī)會(huì)接觸到 Redis 的源碼后再單獨(dú)拿出來(lái)總結(jié)。
例子如下(基于 3.2.× 版本):
127.0.0.1:6379> RPUSH students "zs" "ls" "ww" (integer) 3 127.0.0.1:6379> OBJECT ENCODING students "quicklist"2.3 哈希對(duì)象
(1)哈希對(duì)象編碼
哈希對(duì)象的編碼是 ziplist 或 hashtable。
ziplist 編碼的哈希對(duì)象使用壓縮列表作為底層實(shí)現(xiàn),每當(dāng)有新的鍵值對(duì)要加入到哈希對(duì)象時(shí),程序會(huì)先將保存了鍵的壓縮列表節(jié)點(diǎn)推入到壓縮列表表尾,然后再將保存了值的壓縮列表節(jié)點(diǎn)推入到壓縮列表表尾。因此保存了同一鍵值對(duì)的兩個(gè)節(jié)點(diǎn)總是緊挨在一起, 保存鍵的節(jié)點(diǎn)在前, 保存值的節(jié)點(diǎn)在后。
hashtable 編碼的哈希對(duì)象使用字典作為底層實(shí)現(xiàn),哈希對(duì)象中的每個(gè)鍵值對(duì)都使用一個(gè)字典鍵值對(duì)來(lái)保存。
(2)編碼轉(zhuǎn)換
當(dāng)哈希對(duì)象可以同時(shí)滿足以下兩個(gè)條件時(shí), 哈希對(duì)象使用 ziplist 編碼。
- 鍵和值的字符串長(zhǎng)度都小于 64 字節(jié)
- 鍵值對(duì)數(shù)量小于 512 個(gè)
不能滿足上面兩個(gè)條件的哈希對(duì)象使用 hashtable 編碼。
PS:
上面兩個(gè)條件的上限值是可以在配置文件中修改的,默認(rèn)配置如下:
下面是一個(gè)例子:
127.0.0.1:6379> OBJECT ENCODING student "ziplist" # 存儲(chǔ)一個(gè)大于 64 字節(jié)的值 127.0.0.1:6379> HSET student desc "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz ... " (integer) 1 127.0.0.1:6379> OBJECT ENCODING student "hashtable"2.4 集合對(duì)象
(1)集合對(duì)象編碼
集合對(duì)象的編碼可以是 intset 或者 hashtable 。
inset 編碼的底層實(shí)現(xiàn)是整數(shù)集合,集合對(duì)象包含的所有元素都被保存在整數(shù)集合里。
hashtable 編碼使用字典作為底層實(shí)現(xiàn),字典的每個(gè)鍵都是一個(gè)字符串對(duì)象,每個(gè)字符串對(duì)象包含了一個(gè)集合元素,而字典的值則全部被設(shè)置為 NULL ,可以類比于 Java 中的 HashSet 的實(shí)現(xiàn)。
(2)編碼轉(zhuǎn)換
當(dāng)集合對(duì)象可以滿足以下兩個(gè)條件中的任一個(gè)時(shí), 對(duì)象使用 intset 編碼:
- 集合對(duì)象中的元素都是整數(shù)值
- 集合對(duì)象中元素的數(shù)量不超過(guò) 512 個(gè)
PS:
上面第二個(gè)條件的上限值是可以在配置文件中修改的,默認(rèn)配置如下:
下面是一個(gè)例子:
127.0.0.1:6379> SADD keys "1" "2" (integer) 2 127.0.0.1:6379> OBJECT ENCODING keys "intset" # 添加非整數(shù)元素 127.0.0.1:6379> SADD keys "hei~" (integer) 1 127.0.0.1:6379> OBJECT ENCODING keys "hashtable"2.5有序集合對(duì)象
(1)有序集合對(duì)象編碼
有序集合的編碼可以是 ziplist 或者 skiplist 。
壓縮列表內(nèi)的集合元素按分值從小到大進(jìn)行排序,分值較小的元素被放置在靠近表頭的方向, 分值較大的元素則被放置在靠近表尾的方向。
skiplist 編碼的有序集合對(duì)象使用 zset 結(jié)構(gòu)作為底層實(shí)現(xiàn), 一個(gè) zset 結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表。
為什么 zset 結(jié)構(gòu)要同時(shí)使用字典與跳躍表來(lái)實(shí)現(xiàn)有序集合?
- 如果只使用字典實(shí)現(xiàn)有序集合,查找成員的復(fù)雜度為O(1),但是當(dāng)執(zhí)行范圍操作時(shí),就需要對(duì)字典中保存的元素進(jìn)行排序。
- 如果只使用跳躍表實(shí)現(xiàn)有序集合,執(zhí)行范圍操作時(shí)的所有優(yōu)點(diǎn)都會(huì)被保留,但是執(zhí)行查找元素時(shí),時(shí)間復(fù)雜度上升為O(logN)
- 因此 zset 集成了字典與跳躍表兩者的優(yōu)點(diǎn)來(lái)實(shí)現(xiàn)有序集合對(duì)象。
(2)有序集合對(duì)象編碼轉(zhuǎn)換
當(dāng)有序集合對(duì)象滿足以下條件時(shí),對(duì)象會(huì)使用 ziplist 編碼:
- 有序集合保存的元素?cái)?shù)量小雨 128 個(gè)
- 有序集合保存的所有元素成員長(zhǎng)度小于 64 字節(jié)
PS:
上面第二個(gè)條件的上限值是可以在配置文件中修改的,默認(rèn)配置如下:
三、Other
3.1內(nèi)存回收
C 語(yǔ)言自身不具備內(nèi)存回收功能,因此 Redis 使用引用計(jì)數(shù)(refering counting)實(shí)現(xiàn)內(nèi)存回收機(jī)制。每個(gè)對(duì)象的引用計(jì)數(shù)信息由 redisO此處輸入代碼bject 結(jié)構(gòu)中的 refcount 屬性記錄。
在對(duì)象創(chuàng)建時(shí),引用計(jì)數(shù)值會(huì)被初始化為 1,當(dāng)對(duì)象被程序引用時(shí),引用計(jì)數(shù)值會(huì)加 1,當(dāng)對(duì)象不在被一個(gè)對(duì)象使用時(shí),它的引用計(jì)數(shù)值會(huì)減 1,當(dāng)對(duì)象的引用計(jì)數(shù)值為 0 時(shí),對(duì)象所占的內(nèi)存就會(huì)被回收。
3.2對(duì)象共享
Redis 會(huì)在初始化服務(wù)器時(shí),創(chuàng)建 [0 ,10000) 之間的字符串對(duì)象,當(dāng)服務(wù)器需要用到 0 到 9999 之間的字符串對(duì)象時(shí),服務(wù)器會(huì)共享這些對(duì)象,而不是創(chuàng)建新的對(duì)象,通過(guò)這種方式可以節(jié)約內(nèi)存。
參考書籍
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》黃健宏著
總結(jié)
以上是生活随笔為你收集整理的《Redis 设计与实现》读书笔记-Redis 对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: static 关键字静态导包
- 下一篇: Redis 数据持久化之 RDB