Redis深度離線 - embstr和raw的字符串
在Redis中字符串存儲有兩種方式,embstr和raw兩種形式,不超過44字節的情況下以embstr存儲,超過44字節則以raw形式存儲
<font color="red">注:Redis中底層保證字符串的存儲必然以0結尾,雖然是44個字符但是obj顯示確是45</font>
embstr vs raw
Redis字符串結構體
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
Redis字符串的實現原理是在在字符串頭部額外分配空間存儲上述這樣一個結構體,使用變長結構體存儲長度、容量等信息;視長度而採取不通的結構體。
字符串較短是SDS頭部最少佔用3個字節(sdshdr5廢棄),不過在Redis中上述SDS字符串只是基礎數據結構並不由Redis直接使用
Redis對象結構體
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;
#define LRU_BITS 24
所有Redis對象都有上述結構體,上述結構體長度爲`(4+4+24+4*8+8*8)/8`即16字節;分配一個字符串最少頭部就需要佔用19個字節。
創建字符串對象
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
在創建字符串結構時通過宏定義根據長度區分創建的方式
/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
* string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(const char *ptr, size_t len) {
return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution), or
* alternatively the LFU counter. */
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
return o;
}
創建SDS字符串然後將指針賦給RedisObject的ptr中。
/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
* an object where the sds string is actually an unmodifiable string
* allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
struct sdshdr8 *sh = (void*)(o+1);
o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
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;
}
創建字符串對象時直接創建了`RedisObj+sdsHeader+string`的連續空間,即RedisOjbect的空間和SDS字符串是連在一起的
結構說明
爲什麼是44個字節?
因爲Redis中內存是同意控制分配的,通常是是2、4、8、16、32、64等;`64-19-1=44`就是原因。