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 ,如何解釋?
喜歡的歡迎加公眾號或者留言評論探