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