Redis对字符串进行了封装,定义以下:redis
typedef char *sds; struct sdshdr { unsigned int len; unsigned int free; char buf[]; };
其实就是給字符串前面多加了两个unsigned int来保存字符串信息,len是总长度,free是当前可用长度。
假设当前有一个字符串"aaa",那么经过sds来保存它最少须要多少个字节呢,计算方式以下:数据结构
4(len)+4(free)+3(“aaa”)+1(‘\\0’) = 12
也就是说经过sds来保存一个字符串,会在字符串实际占用之上多占用9个字节来存放额外的信息。函数
sds仅仅是对字符串的封装,Redis还对其封装了一层RedisObject,定义以下:ui
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr; } robj;
其中type占用4bit,encoding占用4bit,lru占用24bit,refcount占用4Byte,ptr占用8Byte,总共占用16字节。以"aaa"为例,封装成sds占用3+9=12字节,再封装成robj,实际占用12+16=28字节,其中ptr指向sds,因此对字符串的robj的内存占用公式能够总结为:指针
N + 9 + 16
其中N为字符串长度;code
注:robj的ptr并不老是保存实际内容,假设字符串为"123",type为 REDIS_STRING,Redis会在tryObjectEncoding函数中判断obj的ptr指向的sds能不能转化成整数,若是能够,那么直接将ptr的值赋为123,并释放以前的sds。而此时encoding为REDIS_ENCODING_INT代表这个ptr保存的是一个整数,因此实际占用仅为一个robj的大小,即16字节。
前面已经介绍了Redis封装的数据结构,如今能够开始计算了,咱们以Redis接收到客户端"set aaa bbb"命令以后,
第一步就是参数解析,直接看关键代码:
首先是协议解析,调用栈是readQueryFromClient -> processInputBuffer -> processMultibulkBuffer,在processMultibulkBuffer中会对每一个参数调用createStringObject生成robj,先来看createStringObject的实现:server
robj *createObject(int type, void *ptr) { robj *o = zmalloc(sizeof(*o)); o->type = type; o->encoding = REDIS_ENCODING_RAW; o->ptr = ptr; o->refcount = 1; /* Set the LRU to the current lruclock (minutes resolution). */ o->lru = server.lruclock; return o; }
那么对于”set”,”aaa”,”bbb”会生成3个robj,各占28个字节,不过对于set这个robj仅仅是用来查找命令lookupCommand使用,后来会释放,对”aaa”这个robj也是会用新的”aaa”的sds来存储,robj也会释放,前两个不计算在内,因此目前仅有”bbb”总共占56字节。索引
第二步,命令的执行,调用栈是processCommand -> call -> proc(此处为setcommand) -> setGenericCommand -> setKey,最终数据是在setKey中被存在db中,这里有一点特殊说明一下,在setcommand调用setGenericCommand以前会调用内存
c->argv[2] = tryObjectEncoding(c->argv[2]);
这里会对命令中的value进行上面说的tryObjectEncoding,此处argv[2]是包含”bbb”的robj,因此这里tryObjectEncoding后,这个robj不会变小,但若是此处是包含”123”的robj,那么通过tryObjectEncoding后,大小会从28变为16(具体缘由参考Object一节_注_部分)element
接着往下看,setKey的定义以下:
void setKey(redisDb *db, robj *key, robj *val) { if (lookupKeyWrite(db,key) == NULL) { dbAdd(db,key,val); } else { dbOverwrite(db,key,val); } incrRefCount(val); removeExpire(db,key); signalModifiedKey(db,key); }
能够看到咱们会将”aaa”和”bbb”的robj指针做为第2、三参数传给它,这里假设这个key以前不存在,那么会调用dbAdd把他插入到db中(db实际是个hash表)
void dbAdd(redisDb *db, robj *key, robj *val) { sds copy = sdsdup(key->ptr); // 将key的robj转换为对应的sds,在dict中的key用 //sds的形式存 int retval = dictAdd(db->dict, copy, val); redisAssertWithInfo(NULL,key,retval == REDIS_OK); if (val->type == REDIS_LIST) signalListAsReady(db, key); } int dictAdd(dict *d, void *key, void *val) { dictEntry *entry = dictAddRaw(d,key); //生成新的dictEntry并赋值key字段 if (!entry) return DICT_ERR; dictSetVal(d, entry, val); //给dictEntry的v字段赋值,指向包含"bbb"的robj return DICT_OK; } dictEntry *dictAddRaw(dict *d, void *key) { int index; dictEntry *entry; dictht *ht; if (dictIsRehashing(d)) _dictRehashStep(d); /* Get the index of the new element, or -1 if * the element already exists. */ if ((index = _dictKeyIndex(d, key)) == -1) return NULL; /* Allocate the memory and store the new entry */ ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; entry = zmalloc(sizeof(*entry)); //生成新的dictEntry entry->next = ht->table[index]; //插入到index位置的桶中 ht->table[index] = entry; ht->used++; /* Set the hash entry fields. */ dictSetKey(d, entry, key); //给dictEntry的key字段赋值,指向"aaa"的sds return entry; }
dbAdd调用dictAdd,最终由dictAdd将一个sds和一个object插入到db中,说是插入,其实就是对这组键值对调用dictAddRaw生成一个dictEntry,并把他插入到按key求hash值索引到的桶中,说到这里已经明确了,这组键值对最终实际保存的位置就是在dictEntry中,它的大小就是最终实际大小,来看定义
typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry;
一个dictEntry的大小是8(key)+8(v)+8(next) = 24字节,key是一个”aaa”的sds指针,v是一个指向包含”bbb”的robj的指针,next是指向对应桶中第二个dictEntry的指针。