對于上一篇文章,我又自己總結(jié)歸納并補(bǔ)充了一下,有了第二篇。
概覽
<<左移
開始之前,我們先準(zhǔn)備點(diǎn)東西:位運(yùn)算
i<<n 總結(jié)為 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種數(shù)據(jù)類型
Redis 3.2 以后SDS數(shù)據(jù)類型有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
結(jié)合上面的位運(yùn)算,我們也能理解這5個數(shù)據(jù)類型的命名規(guī)則。
外部類型String 找 SDS結(jié)構(gòu)
我們現(xiàn)在有定義了5種SDS數(shù)據(jù)類型,那么如何根據(jù)字符串長度找這些類型呢?
或者說輸入的字符串長度和類型有什么關(guān)系?下面我們來看一看他們之間的關(guān)系。
再來看看源碼:
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 }
根據(jù)位運(yùn)算左移公式,我可以得知 1<<8 = 2^8 = 256
那么這里的?。玻担妒侵甘裁矗窟@里的256就是字節(jié)
也就是說:
SDS_TYPE_5 -- 32 Byte
SDS_TYPE_8 -- 256 Byte
SDS_TYPE_16 -- 64KB
SDS_TYPE_32 -- ...
SDS_TYPE_64 -- ...
現(xiàn)在數(shù)據(jù)類型找到了,我們再來看看比較典型的幾種操作。
追加字符串
從使用角度講,追加一般用的頻率很少。所以有多大分配多大。
所以這里追加的話,有兩種大情況:還有剩余 或 不夠用
主要講一下不夠用就要重新申請內(nèi)存,那么我們?nèi)绾稳ド暾垉?nèi)存呢?
這里提供了兩種分配策略:
<1M ,新空間 = 2倍擴(kuò)容; >1M , 新空間 = 累加1M
空間有了,那么我們需要根據(jù)最新的空間長度占用,再找到對應(yīng)的新的SDS數(shù)據(jù)類型。
看一下源碼,增加一下印象:
/* 追加字符串*/ sds sdscatlen(sds s, const void *t, size_t len) { // 當(dāng)前字符串長度 size_t
curlen = sdslen(s); // 按需調(diào)整空間(原來字符串,要追加的長度) s = sdsMakeRoomFor(s,len); // 內(nèi)存不足
if (s == NULL) return NULL; // 追加目標(biāo)字符串到字節(jié)數(shù)組中 memcpy(s+curlen, t, len); //
設(shè)置追加后的長度 sdssetlen(s, curlen+len); // 追加結(jié)束符 s[curlen+len] = '\0'; return s; }
/*空間調(diào)整,注意只是調(diào)整空間,后續(xù)自己組裝字符串*/ sds sdsMakeRoomFor(sds s, size_t addlen) { void
*sh, *newsh; // 當(dāng)前剩下的空間 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ù)據(jù)體 sh =
(char*)s-sdsHdrSize(oldtype); // 新長度 newlen = (len+addlen); // < 1M 2倍擴(kuò)容 if
(newlen < SDS_MAX_PREALLOC) newlen *= 2; // > 1M 擴(kuò)容1M else newlen +=
SDS_MAX_PREALLOC; // 獲取sds 結(jié)構(gòu)類型 type = sdsReqType(newlen); // type5 默認(rèn)轉(zhuǎn)成 type8
if (type == SDS_TYPE_5) type = SDS_TYPE_8; // 頭長度 hdrlen = sdsHdrSize(type); if
(oldtype==type) { // 長度夠用 并且 數(shù)據(jù)結(jié)構(gòu)不變 newsh = s_realloc(sh, hdrlen+newlen+1); if
(newsh == NULL) return NULL; s = (char*)newsh+hdrlen; } else { // 重新申請內(nèi)存 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 和 內(nèi)部類型
外部字符串類型,找到了SDS結(jié)構(gòu),現(xiàn)在到了SDS轉(zhuǎn)內(nèi)部結(jié)構(gòu)
對于字符串類型為什么會分 embstr 和 raw呢?
我們先說一下內(nèi)存分配器:jemalloc、tcmalloc
這來能為仁兄呢分配內(nèi)存的大小都是 2/4/8/16/32/64 字節(jié)
對于redis 來講如何利用并適配好內(nèi)存分配器依然需要好好計算一下。
Redis 給我們實現(xiàn)了很多內(nèi)部數(shù)據(jù)結(jié)構(gòu),這些內(nèi)部數(shù)據(jù)結(jié)構(gòu)得有自己的字描述文件-內(nèi)部結(jié)構(gòu)頭對象
不同對象有不同的type,同一個對象有不同的存儲形式,還有lru緩存淘汰機(jī)制信息,引用計數(shù)器,指向數(shù)據(jù)體的指針。
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned
lru:LRU_BITS; int refcount; void *ptr; } robj;
所以SDS和 內(nèi)部類型的關(guān)系類似于這樣的:
連續(xù)內(nèi)存,和非連續(xù)內(nèi)存
44 字節(jié)
SDS為什么會是這樣的兩種內(nèi)部結(jié)構(gòu)呢?
回憶一下上面提到的:SDS結(jié)構(gòu),最小的應(yīng)該是 SDS_TYPE_8(SDS_TYPE_5默認(rèn)轉(zhuǎn)成8) struc SDS{ int8 capacity;
// 1字節(jié) int8 len; // 1字節(jié) int8 flags; // 1字節(jié) byte[] content; // 內(nèi)容 }
所以從上代碼看出,一個最小的SDS,至少占用3字節(jié).
還有內(nèi)部結(jié)構(gòu)頭:RedisObject typedef struct redisObject { unsigned type:4; // 4bit
unsigned encoding:4; // 4bit unsigned lru:LRU_BITS; // 24bit int
refcount; // 4字節(jié) void *ptr; // 8字節(jié) } robj;
16字節(jié)?。健?2bit(4字節(jié)) + 4字節(jié) + 8字節(jié)
所以一個內(nèi)部類型頭指針大小為:16字節(jié)
再加上最小SDS的3字節(jié),一共 19字節(jié)。也就是說一個最小的字符串所占用的內(nèi)存空間是19字節(jié)
還記得上面我們提到過的內(nèi)存分配器么?(2/4/8/16/32/64 字節(jié))
對,如果要給這個最小19字節(jié)分配內(nèi)存,至少要分配一個32字節(jié)的內(nèi)存。當(dāng)然如果字符串長一點(diǎn),再往下就可以分配到64字節(jié)的內(nèi)存。
以上這種形式被叫做:embstr,這種形式使得 RedisObject和SDS 內(nèi)存地址是連續(xù)的。
那么一旦大于64字節(jié),形式就變成了raw,這種形式使得內(nèi)存不連續(xù),因為SDS已經(jīng)變大,取得大的連續(xù)內(nèi)存得不償失。
再回來討論一下 embstr, 最大64字節(jié)內(nèi)存分配下來,我們實際可以真正存儲字符串的長度是多少呢?--44字節(jié)
64字節(jié),減去RedisObject頭信息19字節(jié),再減去3字節(jié)SDS頭信息,剩下45字節(jié),再去除\0結(jié)尾。這樣最后可以存儲44字節(jié)。
所以 embstr 形式,可以存儲最大字符串長度是44字節(jié)。
關(guān)于字符串最大是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
現(xiàn)在做追加操作,APPEND q scadd ,encoding:raw,長度8
為什么從 sc ----> scscadd 簡單的追加操作內(nèi)部類型會從 embstr -----> raw ,如何解釋?
喜歡的歡迎加公眾號或者留言評論探討
熱門工具 換一換