Redis-對象
在以前的文章中,我們介紹了 Redis 用到的主要數據結構,比如簡單動態字符串、雙端鏈表、字典、壓縮列表、整數集合。
然而 Redis 並沒有直接使用這些數據結構來實現鍵值對的數據庫,而是在這些數據結構之上又包裝了一層 RedisObject(對象),RedisObject 有五種對象:字符串對象、列表對象、哈希對象、集合對象和有序集合對象。
還是跟以前一樣,看幾個問題:
- 使用 RedisObject 對象而不是直接使用雙端隊列、雙端鏈表等數據結構,有什么好處呢?
- RedisObject 的具體結構是什么?
- 五種對象(string、hash、list、set、sort set)對應的 RedisObject 對象有何不同,底層使用的數據結構是什么?
使用 RedisObject 的好處
使用 RedisObject 的優點主要有兩個,分別是:
- 通過不同類型的對象,Redis 可以在執行命令之前,根據對象的類型來判斷一個對象是否可以執行給定的命令。
- 我們可以針對不同的使用場景,為對象設置不同的實現,從而優化內存或查詢速度。
RedisObject 的具體結構是什么?
RedisObject 的源碼如下:
typedef struct redisObject {
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 對象最后一次被訪問的時間
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用計數
int refcount;
// 指向實際值的指針
void *ptr;
} robj;
下面分別解釋一下各個字段的含義:
-
type
type 記錄了對象的類型,所有的類型如下(出自《Redis設計與實現第二版》第八章:對象):
對於 Redis 數據庫保存的鍵值對來說,鍵一定是一個字符串對象,而值則可以使五種對象的其中一種。 -
ptr 指針:指向對象的底層實現數據結構;
-
encoding
encoding 表示 ptr 指向的具體數據結構,即這個對象使用了什么數據結構作為底層實現。
encoding 的取值范圍如下(出自《Redis設計與實現第二版》第八章:對象):
每種類型的對象都至少使用了兩種不同的編碼,對象和編碼的對應關系如下(出自《Redis設計與實現第二版》第八章:對象):
-
refcount
refcount 表示引用計數,由於 C 語言並不具備內存回收功能,所以 Redis 在自己的對象系統中添加了這個屬性,當一個對象的引用計數為0時,則表示該對象已經不被任何對象引用,則可以進行垃圾回收了。
擴展一下:Java中由於引用計數法解決不了循環引用的問題,所以 Java 中使用了可達性分析算法。那么 Redis 有沒有考慮循環引用的問題呢? -
lru:表示對象最后一次被命令程序訪問的時間。
五種對象對應的 RedisObject
字符串對象(string)
字符串對象的 encoding 有三種,分別是:int、raw、embstr。
-
如果一個字符串對象保存的是整數值,並且這個整數值可以用 long 類型標識,那么字符串對象會講整數值保存在 ptr 屬性中,並將 encoding 設置為 int。
假設有如下命令:set number 10086。那么 number 鍵對象的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
-
如果字符串對象保存的是一個字符串值,並且這個字符串的長度大於 32 字節,那么字符串對象將使用一個簡單動態字符串(SDS)來保存這個字符串值,並將對象的編碼設置為 raw。
使用 raw 存儲字符串的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
-
如果字符串對象保存的是一個字符串值,並且這個字符串的長度小於等於 32 字節,那么字符串對象將使用 embstr 編碼的方式來保存這個字符串。
使用 embstr 存儲字符串的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
既然有了 raw 的編碼方式,為什么還會有 embstr 的編碼方式呢?
因為 embstr 的編碼方式有一些優點,如下:
- embstr 編碼將創建字符串對象所需的內存分配次數從 raw 編碼的兩次降低為一次。
- 釋放 embstr 編碼的字符串對象只需要調用一次內存釋放函數,而釋放 raw 編碼的字符串對象需要調用兩次內存釋放函數。
- 因為 embstr 編碼的字符串對象的所有數據都保存在一塊連續的內存里面,所以這種編碼的字符串對象比起 raw ,編碼的字符串對象能夠更好地利用緩存帶來的優勢。
哈希對象(hash)
哈希對象的編碼有兩種,分別是:ziplist、hashtable。
當哈希對象保存的鍵值對數量小於 512,並且所有鍵值對的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 hashtable 存儲。
哈希對象的壓縮列表對應的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
哈希對象的 hashtable 對應的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
列表對象(list)
列表對象的編碼有兩種,分別是:ziplist、linkedlist。
ziplist(壓縮列表)主要是為節省內存而設計的內存結構,它的優點就是節省內存,但缺點就是比其他結構要消耗更多的時間,所以 Redis 在數據量小的時候使用壓縮列表存儲。
當列表的長度小於 512,並且所有元素的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 linkedlist 存儲。
列表對象的壓縮列表對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):
列表對象的鏈表對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):
集合對象(set)
集合對象的編碼有兩種,分別是:intset、hashtable。
intset(整數集合)主要是為節省內存而設計的內存結構,它的優點就是節省內存,但缺點就是比其他結構要消耗更多的時間,所以 Redis 在數據量小的時候使用整數集合存儲。
當集合的長度小於 512,並且所有元素都是整數時,使用整數集合存儲;否則使用 hashtable 存儲。
集合對象的 intset 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):
集合對象的 hashtable 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):
有序集合對象(sort set)
有序集合對象的編碼有兩種,分別是:ziplist、skiplist。
當有序集合的長度小於 128,並且所有元素的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 skiplist 存儲。
有序集合對象的 ziplist 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):
有序集合對象的 skiplist 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):