string的对象编码
string数据类型的对象编码有两种,分别是embstr
和raw
。两种编码的区别并不大,embstr
相对于raw
,内存空间连续。两者的数据格式见下图:
redis的string数据之所以使用embstr
和raw
两种编码格式,是为了当一个string对象的值比较小时,使用一个连续的内存分区存放redisObject对象和sdshdr对象,减少内存分配和回收的消耗。
embstr
编码的string数据只需要创建分配一个内存空间,用于同时存放redisObject对象和sdshdr对象;而raw
编码的string数据需要创建两个内存空间分别存放redisObject对象与sdshdr对象,相对于embstr
编码多了一次内存分配和一次内存回收的消耗。
编码转换
当一个string对象的长度达到临界值时,就会触发编码转换,该临界值是string值长度:44。
如下测试,当string值的长度不大于44时,通过debug命令输出该string对象的编码格式为encoding:embstr
;当string值的长度大于44时,编码格式则为encoding:raw
。
1 2 3 4 5 6 7 8 9
| 127.0.0.1:6379> set key1 12345678901234567890123456789012345678901234 OK 127.0.0.1:6379> debug object key1 Value at:0x7f8428e22080 refcount:1 encoding:embstr serializedlength:21 lru:2200798 lru_seconds_idle:6
127.0.0.1:6379> set key2 123456789012345678901234567890123456789012345 OK 127.0.0.1:6379> debug object key2 Value at:0x7f8428eb3490 refcount:1 encoding:raw serializedlength:21 lru:2200816 lru_seconds_idle:3
|
关键源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #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); }
robj *tryObjectEncoding(robj *o) { ... if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) { robj *emb; if (o->encoding == OBJ_ENCODING_EMBSTR) return o; emb = createEmbeddedStringObject(s,sdslen(s)); decrRefCount(o); return emb; } ... }
|
embstr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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; }
|
raw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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;
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL; } else { o->lru = LRU_CLOCK(); } return o; }
|
临界值44
之所以embstr
和raw
两种编码格式的临界值是44,是因为redis控制一个string对象的内存大小不大于64字节时为一个小字符串,使用一个连续空间能够提高性能。当string对象的内存大小大于64字节时,则认为是一个大字符串,不再适用embstr
编码格式,而使用raw
编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define LRU_BITS 24 typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr; } robj;
struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; uint8_t alloc; unsigned char flags; char buf[]; };
|
由图可见,redisObject对象和sdshdr对象至少需要占19字节,64个字节空间最后留给string值的只有45个字节。而string末尾需要额外一个\0
表示字符串结束,所以实际上留给string值最多只有44个字节。
扩容
redis的string对象直接通过set
等方法创建时,不会创建冗余空间,因为大多数情况下创建的string类型不需要进行追加截取等操作。
但是进行SETRANGE
、APPEND
等对string数据追加截取操作时,redis会对该string数据进行扩容,且扩容后的空间会相对新字符串实际大小多一些冗余空间,用于减少对string数据追加截取操作时的扩容操作从而减少内存空间分配回收的消耗。
扩容规则
- 新字符串大小小于1M时,扩容空间为新字符串大小的2倍即冗余空间等于新字符串大小
- 新字符串大小大于1M时,扩容空间比新字符串大小多1M即冗余空间固定为1M大小
通过预先创建冗余空间,可以减少string数据追加截取时的内存空间分配消耗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 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; }
#define SDS_MAX_PREALLOC (1024*1024) 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); if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC;
type = sdsReqType(newlen);
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; }
|