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