前置了解:
redisDb是Redis存储kv对象的结构,是hash表
存储过程
以set k v命令为例:
当redis接收到该命令后
STEP1 参数解析
调用栈是readQueryFromClient -> processInputBuffer -> processMultibulkBuffer,在processMultibulkBuffer中会对每个参数调用createObject生成robj;
STEP2 命令执行
调用栈是processCommand -> call -> proc(此处为setcommand) -> setGenericCommand -> setKey,最终数据是在setKey中被存在db中
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);
}
可以看到,该函数会将k,v的robj指针传入。假设k之前不存在,则会调用dbAdd将其插入到db中。
STEP3 存入redisDb
dbAdd会将robj形式的k转换为sds类型;之后调用dictAdd,由dictAdd将一个sds类型的k和一个robj类型的v插入到db中。
具体操作为调用dictAddRaw函数生成一个dictEntry,并将dictEntry插入到根据key求的hash值索引对应的桶中。
void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr);
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST ||
val->type == OBJ_ZSET)
signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}
细节说明
在第一步协议解析之后,redis会对每个参数生成对应的robj并且存储在redisClient的argv中,执行完命令之后会调用resetClient来释放这个argv中的每个robj,因此为了实现v对应的robj不会被释放掉,redis实现了对象引用计数:
在resetClient函数中:
void resetClient(client *c) {
redisCommandProc *prevcmd = c->cmd ? c->cmd->proc : NULL;
freeClientArgv(c);
c->reqtype = 0;
c->multibulklen = 0;
c->bulklen = -1;
/* We clear the ASKING flag as well if we are not inside a MULTI, and
* if what we just executed is not the ASKING command itself. */
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
c->flags &= ~CLIENT_ASKING;
/* Remove the CLIENT_REPLY_SKIP flag if any so that the reply
* to the next command will be sent, but set the flag if the command
* we just processed was "CLIENT REPLY SKIP". */
c->flags &= ~CLIENT_REPLY_SKIP;
if (c->flags & CLIENT_REPLY_SKIP_NEXT) {
c->flags |= CLIENT_REPLY_SKIP;
c->flags &= ~CLIENT_REPLY_SKIP_NEXT;
}
}
static void freeClientArgv(client *c) {
int j;
for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]);
c->argc = 0;
c->cmd = NULL;
}
void decrRefCount(robj *o) {
if (o->refcount == 1) {
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
case OBJ_MODULE: freeModuleObject(o); break;
case OBJ_STREAM: freeStreamObject(o); break;
default: serverPanic("Unknown object type"); break;
}
zfree(o);
} else {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
}
}
resetClient调用freeClientArgv来释放argv中的每个robj,真正的操作只是减引用计数,只有为0时才真正释放。
因此,为了保证dictEntry中v的robj不被释放,setKey函数实现了对v的引用计数加1.
而k因为在dbAdd函数中新生成了一个sds,所以可以在resetClient函数里释放k的robj
参考链接:
redis 一组kv实际内存占用计算