欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis對象與redisObject超詳細(xì)分析源碼層

 更新時(shí)間:2022年11月28日 11:27:48   作者:基層搬磚的Panda  
這篇文章主要介紹了Redis對象與redisObject源碼層的分析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧

以下內(nèi)容是基于Redis 6.2.6 版本整理總結(jié)

一、對象

前面幾篇文章,我們介紹了Redis用到的主要的數(shù)據(jù)結(jié)構(gòu),如:sds、list、dict、ziplist、skiplist、inset等。

但是,Redis并沒有直接使用這些數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)key-value數(shù)據(jù)庫,而是基于這些數(shù)據(jù)結(jié)構(gòu)構(gòu)建了一個(gè)對象系統(tǒng)。包括字符串對象、列表對象、哈希對象、集合對象和有序集合對象五種類型的對象。每種對象都使用了至少一種前面提到的數(shù)據(jù)結(jié)構(gòu)。

通過對對象的區(qū)分,Redis可以在執(zhí)行命令前判斷該對象是否能夠執(zhí)行該條命令。為對象設(shè)置不同的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),只要是為了提高效率。

二、對象的類型及編碼

Redis使用對象來表示數(shù)據(jù)中的key和value,每當(dāng)我們在Redis數(shù)據(jù)庫中創(chuàng)建一個(gè)新的鍵值對時(shí),至少會(huì)創(chuàng)建兩個(gè)對象,一個(gè)作用域key,另一個(gè)作用于value。

舉個(gè)栗子:set msg “hello world” 表示分別創(chuàng)建了一個(gè)字符串對象保存“msg”,另一個(gè)字符串對象保存“hello world”:

redisObject 結(jié)構(gòu)體

Redis中的每個(gè)對象由 redisObject 結(jié)構(gòu)體來描述,對象的類型、編碼、內(nèi)存回收、共享對象都需要redisObject的支持,redisObject 結(jié)構(gòu)體定義如下:

#define LRU_BITS 24
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;

下面我們來看看每個(gè)字段的含義:

(1)type: 占4個(gè)比特位,表示對象的類型,有五種類型。當(dāng)我們執(zhí)行type命令時(shí),便是通過type字段獲取對象的類型。

/* 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命令使用示例:

(2)encoding: 占4個(gè)比特位,表示對象使用哪種編碼,redis會(huì)根據(jù)不同的場景使用不同的編碼,大大提高了redis的靈活性和效率。

字符串對象不同編碼類型示例:

(3)lru: 占 24 個(gè)比特位,記錄該對象最后一次被訪問的時(shí)間。千萬別以為這只能在LRU淘汰策略中才用,LFU也是復(fù)用的個(gè)字段。當(dāng)使用LRU時(shí),它保存的上次讀寫的24位unix時(shí)間戳(秒級);使用LFU時(shí),24位會(huì)被分為兩個(gè)部分,16位的分鐘級時(shí)間戳和8位特殊計(jì)數(shù)器,這里就不展開了,詳細(xì)可以注意我后續(xù)的文章。

(4)refcount: 對象的引用計(jì)數(shù),類似于shared_ptr 智能指針的引用計(jì)數(shù),當(dāng)refcount為0時(shí),釋放該對象。

(5)ptr: 指向?qū)ο缶唧w的底層實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)。

三、不同對象編碼規(guī)則

四、redisObject結(jié)構(gòu)各字段使用范例

Redis中操作key的命令大致可以分為兩類:一種是可以操作任何類型的key,如:del type object等等,另外一種是針對特定類型的key只能使用特定的命令。如:LLEN只能用來獲取列表對象的長度。

4.1 類型檢查(type字段)

比如對于LLEN命令,Redis服務(wù)器在執(zhí)行命令之前會(huì)先檢查輸入的key對應(yīng)的的value對象是否為列表類型,即檢查該value對象的type類型是不是OBJ_LIST,如果是才會(huì)執(zhí)行LLEN命令。否則就拒絕執(zhí)行命令并返回操作類型錯(cuò)誤。

4.2 多態(tài)命令的實(shí)現(xiàn)(encoding)

Redis除了會(huì)根據(jù)value對象的類型來判斷對應(yīng)key能否執(zhí)行執(zhí)行命令外,還會(huì)根據(jù)value對象的**編碼方式(encoding字段)**選擇正確的方式來執(zhí)行命令。比如:列表對象的編碼方式有quicklist 和 ziplist兩種,Redis服務(wù)器除了判斷對應(yīng)value對象的類型為列表對象還要根據(jù)具體的編碼選擇正確的LLEN執(zhí)行。

借用面向?qū)ο蟮男g(shù)語來說,可以認(rèn)為LLEN命令是多態(tài)的。只要執(zhí)行LLEN命令的列表鍵,無論value對象的編碼是哪種方式,LLEN命令都可以正常執(zhí)行。實(shí)際上del type 等也是多態(tài)命令。他們和LLEN的區(qū)別在于,前者是基于類型的多態(tài),后者是基于編碼的多態(tài)。

4.3 內(nèi)存回收和共享對象(refcount)

C語言不具備自動(dòng)回收功能,Redis就通過引用計(jì)數(shù)實(shí)現(xiàn)了自己的內(nèi)存回收機(jī)制。具體是由redisObject結(jié)構(gòu)中的refcount字段記錄。對象的引用計(jì)數(shù)會(huì)隨著對象的使用狀態(tài)而不斷變化。

創(chuàng)建一個(gè)新對象時(shí),refcount會(huì)被初始化為1,;當(dāng)對象被另一個(gè)新程序使用時(shí) refcount加1;不被一個(gè)程序使用時(shí)減1;當(dāng)refcount==0時(shí),該對象所占的空間會(huì)被回收。

引用計(jì)數(shù)除了被用來實(shí)現(xiàn)內(nèi)存回收外,還被用來實(shí)現(xiàn)對象共享。比如:

上面我們創(chuàng)建可一個(gè)value為100的key A,并使用object refcount來查看key A的引用計(jì)數(shù),會(huì)看到此時(shí)的refcount為2,這是為什么呢?此時(shí)有兩個(gè)地方引用了這個(gè)value對象,一個(gè)是持有該對象的服務(wù)器程序,另外一個(gè)是共享該value對象的key A。如果,我們再創(chuàng)建一個(gè)value為100 的 key B,那么鍵B也會(huì)指向這個(gè)value對象,使得該對象的引用計(jì)數(shù)變?yōu)?。由此,可以看到,共享value對象的鍵越多,節(jié)約的內(nèi)存就越多。

在創(chuàng)建鍵B的時(shí)候,服務(wù)器考到鍵B要?jiǎng)?chuàng)建的對象是int編碼的字符串對象100,而剛好有個(gè)value為100的共享對象匹配,就直接將鍵B指向該共享對象。因?yàn)槭钦麛?shù)的字符串對象,直接比較即可,如果共享對象是字符串值的對象,要從頭到尾比較每個(gè)字符,時(shí)間復(fù)雜度O(n)。

簡單來說就是,要能使用共享對象,需要先驗(yàn)證該共享對象和要?jiǎng)?chuàng)建的目標(biāo)對象是不是完全一致,如果共享對象保存的值越復(fù)雜,消耗的CPU也就越多,所以Redis值對整數(shù)類型的字符串對象做了共享。沒有共享保存字符串值的字符串對象。

Redis在初始化服務(wù)器是,就創(chuàng)建了一萬個(gè)字符串對象,這些對象包含了0~9999的所有整數(shù)值。當(dāng)你創(chuàng)建了這個(gè)范圍內(nèi)的 字符串對象時(shí),服務(wù)器就會(huì)使用這些共享對象,而不是創(chuàng)建新對象,以節(jié)約內(nèi)存。

4.4 對象的空轉(zhuǎn)時(shí)長(lru)

redisObject結(jié)構(gòu)中的lru字段保存,該對象最后一次被訪問的時(shí)間。 使用object idletime 來查看,注意這個(gè)命令不會(huì)修改對象的lru屬性。

當(dāng)Redis開啟最大內(nèi)存限制,一般為機(jī)器內(nèi)存的一半,如果redis使用的內(nèi)存達(dá)到這個(gè)值,且內(nèi)存淘汰策略使用的是volatile-lru 或者 allkeys-lru,空轉(zhuǎn)時(shí)長較高的那些鍵會(huì)被優(yōu)先釋放。

使用object idletime 查看鍵的空間時(shí)間,單位:秒:

127.0.0.1:6379[1]> keys *
1) "msg"
2) "teacher"
127.0.0.1:6379[1]> object idletime msg
(integer) 71166
127.0.0.1:6379[1]>

五、對象在源碼中的使用

5.1 字符串對象

5.1.1字符串對象創(chuàng)建

// code location: src/object.c  
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
// 創(chuàng)建 strintg 對象
robj *createStringObject(const char *ptr, size_t len) {
	// 如果待保存的字符串的長度小于等于44,使用 embstr 編碼
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else // 否則使用 raw 編碼
        return createRawStringObject(ptr,len);
}
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
	// 申請 robj + sdshdr + data + 1 的空間
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
    struct sdshdr8 *sh = (void*)(o+1);
    o->type = OBJ_STRING;      // 設(shè)置類型
    o->encoding = OBJ_ENCODING_EMBSTR; // 設(shè)置編碼
    o->ptr = sh+1;
    o->refcount = 1;
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

從 createEmbeddedStringObject 函數(shù)可以看到,該對象是robj和sds的結(jié)合體,將sds直接放入到robj里,這也是嵌入式編碼embstr的由來。

為什么要限制44字節(jié)呢?因?yàn)閞obj結(jié)構(gòu)體占16個(gè)字節(jié),sdshdr結(jié)構(gòu)體占3個(gè)字節(jié),最后結(jié)尾的‘\0’占一個(gè)字節(jié),限制44個(gè)字節(jié),就能保證64個(gè)字節(jié)里能放下所有內(nèi)容(16+3+1+44 = 64)。

5.1.2 字符串對象編碼

Redis將節(jié)省內(nèi)存做到了極致,它的作者對字符串對象又做了特殊的編碼處理,以進(jìn)一步達(dá)到節(jié)省空間的目的。編碼處理過程及代碼注釋如下:

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;
    /* Make sure this is a string object, the only type we encode
     * in this function. Other types use encoded memory efficient
     * representations but are handled by the commands implementing
     * the type. */
    // 這里只對string對象進(jìn)行編碼,其他類型的編碼都有對應(yīng)的具體實(shí)現(xiàn)處理
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    /* We try some specialized encoding only for objects that are
     * RAW or EMBSTR encoded, in other words objects that are still
     * in represented by an actually array of chars. */
    // 非sds string對象,直接返回原對象
    if (!sdsEncodedObject(o)) return o;
    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace. */
    // 如果該對象由其他對象共享,不能編碼,如果編碼可能影響到其他對象的使用
     if (o->refcount > 1) return o;
    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 20 chars is not
     * representable as a 32 nor 64 bit integer. */
    // 檢查能否把一個(gè)字符串表示為長整型數(shù),長度要小于等于20
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well. */
         // 如果可以被編碼為long型,且編碼后的值小于OBJ_SHARED_INTEGERS(10000),且未配
         // 置LRU替換淘汰策略, 就使用這個(gè)數(shù)的共享對象,相當(dāng)于所有小于10000的數(shù)都是用的同一個(gè)robj
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == OBJ_ENCODING_RAW) {
                sdsfree(o->ptr);
                o->encoding = OBJ_ENCODING_INT;
                o->ptr = (void*) value;
                return o;
            } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
                decrRefCount(o);
                return createStringObjectFromLongLongForValue(value);
            }
        }
    }
    // 不能轉(zhuǎn)為long的字符串
    /* If the string is small and is still RAW encoded,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses. */
    // 如果字符串的長度太小,小于等于44
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;
        // 如果當(dāng)前編碼是embstr,直接返回原對象,否則轉(zhuǎn)為embstr編碼,返回
        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s));
        decrRefCount(o);
        return emb;
    }
    /* We can't encode the object...
     *
     * Do the last try, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
    // 如果前面編碼沒有成功,這里做最后一步,當(dāng)編碼類型為raw,且它的sds可用空間超過10%,
    // 嘗試釋放調(diào)這部分內(nèi)存
    trimStringObjectIfNeeded(o);
    /* Return the original object. */
    // 返回原對象
    return o;
}

5.1.3 字符串對象解碼

有編碼就有解碼,實(shí)際上只需要那些可以轉(zhuǎn)為整型類型的字符串傳進(jìn)行解碼,解碼代碼及注釋如下:

robj *getDecodedObject(robj *o) {
    robj *dec;
	// 如果編碼是 embstr 和 raw ,只是把引用計(jì)數(shù)加一,然后返回原對象
    if (sdsEncodedObject(o)) {
        incrRefCount(o);
        return o;
    }
    // 如果編碼是 int 進(jìn)行解碼,返回新的對象
    if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) {
        char buf[32];
        ll2string(buf,32,(long)o->ptr);
        dec = createStringObject(buf,strlen(buf));
        return dec;
    } else {
        serverPanic("Unknown encoding type");
    }
}

5.1.4 redis對象引用計(jì)數(shù)及自動(dòng)清理

void incrRefCount(robj *o) {
    if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
        o->refcount++; // 引用計(jì)數(shù)加一
    } else {
        if (o->refcount == OBJ_SHARED_REFCOUNT) {
            /* Nothing to do: this refcount is immutable. */
        } else if (o->refcount == OBJ_STATIC_REFCOUNT) {
            serverPanic("You tried to retain an object allocated in the stack");
        }
    }
}
// 減少引用計(jì)數(shù)
void decrRefCount(robj *o) {
	// 釋放空間
    if (o->refcount == 1) {
        switch(o->type) {
        case OBJ_STRING: freeStringObject(o); break;
        case OBJ_LIST: freeListObject(o); break;
        case OBJ_SET: freeSetObject(o); break;
        case OBJ_ZSET: freeZsetObject(o); break;
        case OBJ_HASH: freeHashObject(o); break;
        case OBJ_MODULE: freeModuleObject(o); break;
        case OBJ_STREAM: freeStreamObject(o); break;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
        if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--; // 計(jì)數(shù)減一
    }
}

六、總結(jié)

  • redis對象為所有類型的value提供了統(tǒng)一的封裝
  • 為對象的淘汰策略保存相關(guān)信息
  • 實(shí)現(xiàn)引用計(jì)數(shù)及內(nèi)存自動(dòng)釋放功能

到此這篇關(guān)于Redis對象與redisObject超詳細(xì)分析源碼層的文章就介紹到這了,更多相關(guān)Redis對象與redisObject內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談為什么單線程的redis那么快

    淺談為什么單線程的redis那么快

    本文主要介紹了為什么單線程的redis那么快,主要介紹了幾點(diǎn)原因,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Redis使用元素刪除的布隆過濾器來解決緩存穿透問題

    Redis使用元素刪除的布隆過濾器來解決緩存穿透問題

    本文主要介紹了Redis使用元素刪除的布隆過濾器來解決緩存穿透問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Redis高并發(fā)情況下并發(fā)扣減庫存項(xiàng)目實(shí)戰(zhàn)

    Redis高并發(fā)情況下并發(fā)扣減庫存項(xiàng)目實(shí)戰(zhàn)

    本文主要介紹了Redis高并發(fā)情況下并發(fā)扣減庫存項(xiàng)目實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • 一文帶你深入理解Redis的主從架構(gòu)

    一文帶你深入理解Redis的主從架構(gòu)

    Redis主從架構(gòu)是一種分布式數(shù)據(jù)庫架構(gòu),它包括一個(gè)主節(jié)點(diǎn)(Master)和一個(gè)或多個(gè)從節(jié)點(diǎn)(Slave),主節(jié)點(diǎn)處理所有寫操作,從節(jié)點(diǎn)負(fù)責(zé)復(fù)制主節(jié)點(diǎn)的數(shù)據(jù)并處理讀請求,本文將帶大家深入理解Redis主從架構(gòu),需要的朋友可以參考下
    2023-09-09
  • Java Socket實(shí)現(xiàn)Redis客戶端的詳細(xì)說明

    Java Socket實(shí)現(xiàn)Redis客戶端的詳細(xì)說明

    socket編程是一門技術(shù),它主要是在網(wǎng)絡(luò)通信中經(jīng)常用到.這篇文章主要介紹了如何用Java Socket實(shí)現(xiàn)一個(gè)簡單的Redis客戶端,需要的朋友可以參考下
    2021-05-05
  • NestJS+Redis實(shí)現(xiàn)手寫一個(gè)限流器

    NestJS+Redis實(shí)現(xiàn)手寫一個(gè)限流器

    限流是大型系統(tǒng)必備的保護(hù)措施,本文將結(jié)合redis , lua 腳本 以及 Nestjs Guard 來實(shí)現(xiàn) 限流的效果,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • 通過redis的腳本lua如何實(shí)現(xiàn)搶紅包功能

    通過redis的腳本lua如何實(shí)現(xiàn)搶紅包功能

    這篇文章主要給大家介紹了關(guān)于通過redis的腳本lua如何實(shí)現(xiàn)搶紅包功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • 詳解Redis如何多規(guī)則限流和防重復(fù)提交

    詳解Redis如何多規(guī)則限流和防重復(fù)提交

    市面上很多介紹redis如何實(shí)現(xiàn)限流的,但是大部分都有一個(gè)缺點(diǎn),就是只能實(shí)現(xiàn)單一的限流,但是如果想一個(gè)接口兩種規(guī)則都需要滿足呢,使用本文就來介紹一下redis實(shí)現(xiàn)分布式多規(guī)則限流的方式吧
    2023-12-12
  • 基于redis+lua進(jìn)行限流的方法

    基于redis+lua進(jìn)行限流的方法

    這篇文章主要介紹了基于redis+lua進(jìn)行限流,通過實(shí)例代碼詳細(xì)介紹了lua+redis進(jìn)行限流的做法,開發(fā)環(huán)境使用idea+redis+lua,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • 虛擬機(jī)下的Redis無法訪問報(bào)錯(cuò)500解決方法

    虛擬機(jī)下的Redis無法訪問報(bào)錯(cuò)500解決方法

    這篇文章主要介紹了虛擬機(jī)下的Redis無法訪問,報(bào)錯(cuò)500解決方法,由于我的redis是在虛擬機(jī)下安裝的,無法訪問redis的原因是因?yàn)樘摂M機(jī)的ip地址和主機(jī)不同,文中通過圖文結(jié)合給出了詳細(xì)的解決方法,需要的朋友可以參考下
    2024-02-02

最新評論