Redis 學習筆記(篇五):對象(RedisObject)


Redis-對象

在以前的文章中,我們介紹了 Redis 用到的主要數據結構,比如簡單動態字符串、雙端鏈表、字典、壓縮列表、整數集合。
然而 Redis 並沒有直接使用這些數據結構來實現鍵值對的數據庫,而是在這些數據結構之上又包裝了一層 RedisObject(對象),RedisObject 有五種對象:字符串對象、列表對象、哈希對象、集合對象和有序集合對象。

還是跟以前一樣,看幾個問題:

  • 使用 RedisObject 對象而不是直接使用雙端隊列、雙端鏈表等數據結構,有什么好處呢?
  • RedisObject 的具體結構是什么?
  • 五種對象(string、hash、list、set、sort set)對應的 RedisObject 對象有何不同,底層使用的數據結構是什么?

使用 RedisObject 的好處

使用 RedisObject 的優點主要有兩個,分別是:

  1. 通過不同類型的對象,Redis 可以在執行命令之前,根據對象的類型來判斷一個對象是否可以執行給定的命令。
  2. 我們可以針對不同的使用場景,為對象設置不同的實現,從而優化內存或查詢速度。

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;

下面分別解釋一下各個字段的含義:

  1. type
    type 記錄了對象的類型,所有的類型如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》61頁
    對於 Redis 數據庫保存的鍵值對來說,鍵一定是一個字符串對象,而值則可以使五種對象的其中一種。

  2. ptr 指針:指向對象的底層實現數據結構;

  3. encoding
    encoding 表示 ptr 指向的具體數據結構,即這個對象使用了什么數據結構作為底層實現。
    encoding 的取值范圍如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》62頁
    每種類型的對象都至少使用了兩種不同的編碼,對象和編碼的對應關系如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》63頁

  4. refcount
    refcount 表示引用計數,由於 C 語言並不具備內存回收功能,所以 Redis 在自己的對象系統中添加了這個屬性,當一個對象的引用計數為0時,則表示該對象已經不被任何對象引用,則可以進行垃圾回收了。
    擴展一下:Java中由於引用計數法解決不了循環引用的問題,所以 Java 中使用了可達性分析算法。那么 Redis 有沒有考慮循環引用的問題呢?

  5. lru:表示對象最后一次被命令程序訪問的時間。

五種對象對應的 RedisObject

字符串對象(string)

字符串對象的 encoding 有三種,分別是:int、raw、embstr。

  1. 如果一個字符串對象保存的是整數值,並且這個整數值可以用 long 類型標識,那么字符串對象會講整數值保存在 ptr 屬性中,並將 encoding 設置為 int。
    假設有如下命令:set number 10086。那么 number 鍵對象的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》64頁

  2. 如果字符串對象保存的是一個字符串值,並且這個字符串的長度大於 32 字節,那么字符串對象將使用一個簡單動態字符串(SDS)來保存這個字符串值,並將對象的編碼設置為 raw。
    使用 raw 存儲字符串的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》65頁

  3. 如果字符串對象保存的是一個字符串值,並且這個字符串的長度小於等於 32 字節,那么字符串對象將使用 embstr 編碼的方式來保存這個字符串。
    使用 embstr 存儲字符串的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):
    《Redis設計與實現第二版》65頁

既然有了 raw 的編碼方式,為什么還會有 embstr 的編碼方式呢?
因為 embstr 的編碼方式有一些優點,如下:

  • embstr 編碼將創建字符串對象所需的內存分配次數從 raw 編碼的兩次降低為一次。
  • 釋放 embstr 編碼的字符串對象只需要調用一次內存釋放函數,而釋放 raw 編碼的字符串對象需要調用兩次內存釋放函數。
  • 因為 embstr 編碼的字符串對象的所有數據都保存在一塊連續的內存里面,所以這種編碼的字符串對象比起 raw ,編碼的字符串對象能夠更好地利用緩存帶來的優勢。

哈希對象(hash)

哈希對象的編碼有兩種,分別是:ziplist、hashtable。
當哈希對象保存的鍵值對數量小於 512,並且所有鍵值對的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 hashtable 存儲。

哈希對象的壓縮列表對應的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

哈希對象的 hashtable 對應的示意圖如下(出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

列表對象(list)

列表對象的編碼有兩種,分別是:ziplist、linkedlist。

ziplist(壓縮列表)主要是為節省內存而設計的內存結構,它的優點就是節省內存,但缺點就是比其他結構要消耗更多的時間,所以 Redis 在數據量小的時候使用壓縮列表存儲。

當列表的長度小於 512,並且所有元素的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 linkedlist 存儲。

列表對象的壓縮列表對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

列表對象的鏈表對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

集合對象(set)

集合對象的編碼有兩種,分別是:intset、hashtable。

intset(整數集合)主要是為節省內存而設計的內存結構,它的優點就是節省內存,但缺點就是比其他結構要消耗更多的時間,所以 Redis 在數據量小的時候使用整數集合存儲。

當集合的長度小於 512,並且所有元素都是整數時,使用整數集合存儲;否則使用 hashtable 存儲。

集合對象的 intset 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

集合對象的 hashtable 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

有序集合對象(sort set)

有序集合對象的編碼有兩種,分別是:ziplist、skiplist。

當有序集合的長度小於 128,並且所有元素的長度都小於 64 字節時,使用壓縮列表存儲;否則使用 skiplist 存儲。

有序集合對象的 ziplist 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》

有序集合對象的 skiplist 對應的示意圖如下出自《Redis設計與實現第二版》第八章:對象):

《Redis設計與實現第二版》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM