1. 程式人生 > >Redis開發與運維:SDS與44位元組深入理解

Redis開發與運維:SDS與44位元組深入理解

對於上一篇文章,我又自己總結歸納並補充了一下,有了第二篇。

概覽

<<左移

開始之前,我們先準備點東西:位運算

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

所以

1<<5 = 2^5

1<<8 = 2^8

1<<16 = 2^16

1<<32 = 2^32

1<<64 = 2^64

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個數據型別的命名規則。

外部型別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  ,如何解釋?

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