Redis開發與運維:SDS與embstr、raw 深刻理解

對於上一篇文章,我又本身總結概括並補充了一下,有了第二篇。redis

概覽

<<左移

開始以前,咱們先準備點東西:位運算數組

i<<n 總結爲  i*2^n

因此緩存

1<<5 = 2^5數據結構

1<<8 = 2^8curl

1<<16 = 2^16this

1<<32 = 2^32url

1<<64 = 2^643d

SDS 5種數據類型

Redis 3.2 之後SDS數據類型有5個指針

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

結合上面的位運算,咱們也能理解這5個數據類型的命名規則。code

外部類型String 找 SDS結構

咱們如今有定義了5種SDS數據類型,那麼如何根據字符串長度找這些類型呢?

或者說輸入的字符串長度和類型有什麼關係?下面咱們來看一看他們之間的關係。

再來看看源碼:

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5)
        return SDS_TYPE_5;
    if (string_size < 1<<8)
        return SDS_TYPE_8;
    if (string_size < 1<<16)
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
    if (string_size < 1ll<<32)
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else
    return SDS_TYPE_32;
#endif
}

根據位運算左移公式,我能夠得知 1<<8 = 2^8 = 256

那麼這裏的 256是指什麼?這裏的256就是字節

也就是說:
SDS_TYPE_5 -- 32 Byte
SDS_TYPE_8 -- 256 Byte
SDS_TYPE_16 -- 64KB
SDS_TYPE_32 -- ...
SDS_TYPE_64 -- ...

如今數據類型找到了,咱們再來看看比較典型的幾種操做。

 追加字符串

從使用角度講,追加通常用的頻率不多。因此有多大分配多大。

因此這裏追加的話,有兩種大狀況:還有剩餘 或 不夠用

主要講一下不夠用就要從新申請內存,那麼咱們如何去申請內存呢?

這裏提供了兩種分配策略:

<1M ,新空間 = 2倍擴容;

>1M , 新空間 = 累加1M

空間有了,那麼咱們須要根據最新的空間長度佔用,再找到對應的新的SDS數據類型。

看一下源碼,增長一下印象:

/* 追加字符串*/
sds sdscatlen(sds s, const void *t, size_t len) {
    // 當前字符串長度
    size_t curlen = sdslen(s); 
    // 按需調整空間(原來字符串,要追加的長度)
    s = sdsMakeRoomFor(s,len);
    // 內存不足
    if (s == NULL) return NULL;
    // 追加目標字符串到字節數組中
    memcpy(s+curlen, t, len);
    // 設置追加後的長度
    sdssetlen(s, curlen+len);
    // 追加結束符
    s[curlen+len] = '\0';
    return s;
}
/*空間調整,注意只是調整空間,後續本身組裝字符串*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    // 當前剩下的空間
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    /* 空間足夠 */
    if (avail >= addlen) return s;
    // 長度
    len = sdslen(s);
    // 真正的數據體
    sh = (char*)s-sdsHdrSize(oldtype);
    // 新長度
    newlen = (len+addlen);
    // < 1M 2倍擴容
    if (newlen < SDS_MAX_PREALLOC)
        newlen *= 2;
    // > 1M 擴容1M
    else
        newlen += SDS_MAX_PREALLOC;
    // 獲取sds 結構類型
    type = sdsReqType(newlen);

    // type5 默認轉成 type8
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
    // 頭長度
    hdrlen = sdsHdrSize(type);
    if (oldtype==type) { // 長度夠用 而且 數據結構不變 
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        // 從新申請內存
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}

SDS 和 內部類型

外部字符串類型,找到了SDS結構,如今到了SDS轉內部結構

對於字符串類型爲何會分 embstr 和 raw呢?

咱們先說一下內存分配器:jemalloc、tcmalloc

這來能爲仁兄呢分配內存的大小都是 2/4/8/16/32/64 字節

對於redis 來說如何利用並適配好內存分配器依然須要好好計算一下。

Redis 給咱們實現了不少內部數據結構,這些內部數據結構得有本身的字描述文件-內部結構頭對象
不一樣對象有不一樣的type,同一個對象有不一樣的存儲形式,還有lru緩存淘汰機制信息,引用計數器,指向數據體的指針。

typedef struct redisObject {
    unsigned type:4;       
    unsigned encoding:4;    
    unsigned lru:LRU_BITS;  
    int refcount;      
    void *ptr;            
} robj;

因此SDS和 內部類型的關係相似於這樣的:

連續內存,和非連續內存

44 字節

SDS爲何會是這樣的兩種內部結構呢?

回憶一下上面提到的:SDS結構,最小的應該是 SDS_TYPE_8(SDS_TYPE_5默認轉成8)
struc SDS{
    int8 capacity;  // 1字節
    int8 len;       // 1字節
    int8 flags;     // 1字節
    byte[] content; // 內容
}

因此從上代碼看出,一個最小的SDS,至少佔用3字節.

還有內部結構頭:RedisObject
typedef struct redisObject {
    unsigned type:4;        // 4bit
    unsigned encoding:4;    // 4bit
    unsigned lru:LRU_BITS;  // 24bit
    int refcount;       // 4字節
    void *ptr;              // 8字節
} robj;

16字節 = 32bit(4字節) + 4字節 + 8字節

因此一個內部類型頭指針大小爲:16字節

再加上最小SDS的3字節,一共 19字節。也就是說一個最小的字符串所佔用的內存空間是19字節

還記得上面咱們提到過的內存分配器麼?(2/4/8/16/32/64 字節)

對,若是要給這個最小19字節分配內存,至少要分配一個32字節的內存。固然若是字符串長一點,再往下就能夠分配到64字節的內存。

以上這種形式被叫作:embstr,這種形式使得 RedisObject和SDS 內存地址是連續的。

那麼一旦大於64字節,形式就變成了raw,這種形式使得內存不連續,由於SDS已經變大,取得大的連續內存得不償失。

再回來討論一下 embstr, 最大64字節內存分配下來,咱們實際能夠真正存儲字符串的長度是多少呢?--44字節

64字節,減去RedisObject頭信息19字節,再減去3字節SDS頭信息,剩下45字節,再去除\0結尾。這樣最後能夠存儲44字節。

因此 embstr 形式,能夠存儲最大字符串長度是44字節。

關於字符串最大是512M

Strings
Strings are the most basic kind of Redis value. Redis Strings are binary safe, 

this means that a Redis string can contain any kind of data, 

for instance a JPEG image or a serialized Ruby object.

A String value can be at max 512 Megabytes in length.

 出個題(redis 5.0.5版本)

SET q sc

encoding:embstr,長度爲3

如今作追加操做,APPEND q scadd ,encoding:raw,長度8

爲何從 sc  ---->  scscadd 簡單的追加操做內部類型會從 embstr ----->   raw  ,如何解釋?

喜歡的歡迎加公衆號或者留言評論探討

相關文章
相關標籤/搜索