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的指針。