【最完整系列】Redis-結構篇-通用對象

注意:本系列文章分析的 Redis 源碼版本:github.com/Sidfate/red… ,是文章發佈時間的最新版。git

前面在講字典的時候咱們曾提到過整個 redis 的 db 中 key-value 結構也是一個 dict,dict 的 key 咱們知道是一個字符串 SDS(參照我以前的文章),那麼 value 的話能夠對應多種類型,爲了統一管理 value 的多種類型,redis 提供了一個通用的對象結構 redisObject。github

源碼結構

redisObject 的源碼結構以下:redis

typedef struct redisObject {
        unsigned type:4;
        unsigned encoding:4;
        unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */
        int refcount;
        void *ptr;
    } robj;
複製代碼
屬性 含義
type 對象的數據類型。佔 4 個位。
encoding 對象的編碼。佔 4 個位,它的全部取值在上面也給出了。
lru 對象的空轉時間。佔 24 個位。
refcount 引用計數。它容許 robj 對象在某些狀況下被共享。
ptr 數據指針,指向真正的數據。好比,一個表明 string 的 robj,它的 ptr 可能指向一個 sds 結構;一個表明 list 的 robj,它的 ptr 可能指向一個 quicklist。

數據類型 type 其實咱們以前有提到的 5 種經常使用結構,它的取值以下:算法

/* The actual Redis Object */
    #define OBJ_STRING 0 /* String object. */
    #define OBJ_LIST 1 /* List object. */
    #define OBJ_SET 2 /* Set object. */
    #define OBJ_ZSET 3 /* Sorted set object. */
    #define OBJ_HASH 4 /* Hash object. */
複製代碼

那麼有了 type 爲何還要 encoding 呢,這是由於一種 type 下可能存在多個 encoding 方式。就像咱們在 字符串章節中提到的當 type = OBJ_STRING 的時候,表示這個 robj 存儲的是一個 string,這時 encoding 能夠是下面3種中的一種:shell

  • OBJ_ENCODING_RAW
  • OBJ_ENCODING_INT
  • OBJ_ENCODING_EMBSTR

經過 encoding 也能夠看出來目前 redis 中針對數據類型使用的數據結構,本系列結構篇的源碼分析也是從這個角度出發的。如下是 redis 源碼中定義的 encoding,你們能夠參照以前的文章對應一下 type 和 encoding 的關係 :服務器

/* Objects encoding. Some kind of objects like Strings and Hashes can be * internally represented in multiple ways. The 'encoding' field of the object * is set to one of this fields for this object. */
    #define OBJ_ENCODING_RAW 0 /* Raw representation */
    #define OBJ_ENCODING_INT 1 /* Encoded as integer */
    #define OBJ_ENCODING_HT 2 /* Encoded as hash table */
    #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
    #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
    #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
    #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
    #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
    #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
複製代碼

關於 lru 字段,我簡單介紹下,就是用 LRU 算法算出來的對象的空閒時間,至於 LRU 算法是什麼以及這個字段的具體使用請關注我以後的文章,敬請期待。數據結構

接下來我要重點說明下 refcount 引用計數。源碼分析

refcount 引用計數

若是你們看了以前的文章能夠發現 redis 在內存使用上作了太多優化,而不少語言內部針對內存優化都會有自動的內存回收機制,可是 c 語言沒有,因此 redis 經過對象的引用計數 refcount 管理本身實現了一套回收機制。簡單來講就是判斷對象的 refcount 變化來判斷是否是須要自動釋放對象並進行內存回收。優化

對象的引用計數信息會隨着對象的使用狀態而不斷變化:ui

  • 在建立一個新對象時, 引用計數的值會被初始化爲 1 ;
  • 當對象被一個新程序使用時, 它的引用計數值會被增一;
  • 當對象再也不被一個程序使用時, 它的引用計數值會被減一;
  • 當對象的引用計數值變爲 0 時, 對象所佔用的內存會被釋放。

關於 refcount 這個詞其實已經在以前的文章中出現過了,不知道你們發現沒,嘗試下如下命令:

> set test_str "abc"
    > debug object test_str
    Value at:0x7fb47bc09150 refcount:1 encoding:embstr serializedlength:4 lru:3567105 lru_seconds_idle:3
    > set test_str 100
    OK
    > debug object test_str
    Value at:0x7fb47bd06320 refcount:2147483647 encoding:int serializedlength:2 lru:3567067 lru_seconds_idle:754
複製代碼

有沒有發現奇怪的地方?將 test_str 設置成 「abc」 時咱們看到的 refcount 是 1,由於咱們新建了這個對象,因此他的 refcount 初始化爲 1。可是爲何咱們從新將 test_str 設置成 100 後,refcount 的值忽然變成了 2147483647,這麼大一個數字,緣由就在於 100 這個數字。

默認狀況下, Redis 會在初始化服務器時,建立一萬個字符串對象, 這些對象包含了從 0 到 9999 的全部整數值, 當服務器須要用到值爲 0 到 9999 的字符串對象時, 服務器就會使用這些共享對象, 而不是新建立對象。

共享對象帶來的就是不須要爲這些小整數對象頻繁的分配內存,並且不只能夠用到字符串的值,也能用在 dict 中的值,等等。這些小整數對象的引用計數恆等於 INT_MAX,也就是咱們所看到的 2147483647。

爲何 Redis 不共享包含字符串的對象?

試想一下,若是咱們爲一個字符串 「abc」 也建立共享對象,那麼若是我想用到它,我就須要在建立新字符串對象的時候比較值是否是與 「abc」 相等,最壞的狀況是 O(N),那就會消耗必定的 cpu 時間,若是字符串共享多了就跟不用說了。而整數咱們只要比較大小就好了,複雜度只須要 O(1)

雖然共享對象能夠減小內存分配,方便管理,可是權衡下來,只共享了整數,何況很差肯定須要共享哪些字符串或者非數字對象有哪些。

相關文章
相關標籤/搜索