redis6.2.6
一、对象
redis的每个key和value都是一个对象,对象的定义如下。
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;
type表示当前是什么对象
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
#define OBJ_MODULE 5
#define OBJ_STREAM 6
encoding表示当前对象如何编码
#define OBJ_ENCODING_RAW 0
#define OBJ_ENCODING_INT 1
#define OBJ_ENCODING_HT 2
#define OBJ_ENCODING_ZIPMAP 3
#define OBJ_ENCODING_LINKEDLIST 4
#define OBJ_ENCODING_ZIPLIST 5
#define OBJ_ENCODING_INTSET 6
#define OBJ_ENCODING_SKIPLIST 7
#define OBJ_ENCODING_EMBSTR 8
#define OBJ_ENCODING_QUICKLIST 9
#define OBJ_ENCODING_STREAM 10
lru表示访问时间(访问次数),用于淘汰策略使用 refcount引用计数 ptr具体的值, 比如key本身的值,value的值 使用了位域,减少不必要的内存开销,整个对象在x86 64bit系统下大小为16字节
二、字符串对象
redis的每个key都是一个字符串对象,而字符串对象又分为普通字符串对象和嵌入式字符串对象。 type: OBJ_STRING,encoding: OBJ_ENCODING_RAW type: OBJ_STRING,encoding: OBJ_ENCODING_EMBSTR
2.1 原始字符串对象
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;
}
可以看出rawStringObject对象和key是分别分配空间的,key是通过sdsnewlen函数分配的空间,当key很小时,频繁的创建这些对象,将容易造成内存碎片,因此引入了嵌入式字符串对象。
2.2 嵌入式字符串对象
当key很小时,将对象和key当作一个整体,分配一个连续的空间,这样就能减少内存碎片的产生。
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;
}
2.3 什么时候使用嵌入式字符串对象
#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);
}
当字符串长度小于等于44字节时,使用嵌入式字符串对象。
2.4 为啥是44字节
- 首先redisObject大小16字节
- sds最新的结构体大小为3+1(\0字节)=4
这里就已经占用20字节了。而对于内存的分配,实际上不是需要多少就分配多少,一般都是2的次方并且大于等于需要分配的大小的最小值,比如需要5字节,实际分配的8字节(最接近5的2的次方)。redis默认使用jemalloc,其中分配小内存分配4,8,16,32,64。 所以能最大分配的最小空间是64,64-16-4 = 44 , 所以最大的字符串长度为44,小于等于44的都可以在一个块中分配。
|