Redis源码分析系列二十四: 7 set---setCommand解析

这个命令应该是打通不少命令的关键点,估计也是用的最多的命令了。 数据结构

因此必须一字一句的来剖析这个命令的本质! 函数

我只能说:全部的反动派都是纸老虎! 源码分析

注意:这个操做的数据影响server.db[index].dict哈希表。 学习

看完这个函数以后:我只想说:若是设置了expire,也会影响server.db[index].expires哈希表 ui

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 编码


 int j;
//设置一个整型变量


robj *expire = NULL;
//设置一个robj类型的指针变量


int unit = UNIT_SECONDS;
//设置unit默认为1 spa


int flags = REDIS_SET_NO_FLAGS;
//默认设置flags为0
//自定义检查点: 1 2 3 指针

~~~~~~~~~~~~~~~~~~~~~~~~~~~ code

for (j = 3; j < c->argc; j++) 
{


//从set key value后面的参数中开始提取内容

char *a = c->argv[j]->ptr;
//指向当前参数的值

robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
//获取下一个参数,可能为NULL server

~~~~~~~~~~~~~~~~~~~~~~~~~~

if ((a[0] == 'n' || a[0] == 'N') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') 
{
flags |= REDIS_SET_NX;


//若是是n|N,  x|X,则设置flags标识加上1

~~~~~~~~~~~~~~~~~~~~~~~~~~

else if ((a[0] == 'x' || a[0] == 'X') &&
  (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') 
{
flags |= REDIS_SET_XX;


//若是是x|X, x|X,则设置flags标识加上2

~~~~~~~~~~~~~~~~~~~~~~~~~~

else if ((a[0] == 'e' || a[0] == 'E') &&
  (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) 
{
unit = UNIT_SECONDS;

expire = next;


j++;
}


//若是是e|E, x|X,则设置unit= 0,expire指向下一个节点

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

else if ((a[0] == 'p' || a[0] == 'P') &&
  (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) 
{

unit = UNIT_MILLISECONDS;

expire = next;

j++;


//若是是p|P, x|X,则设置unit = 1,expire指向下一个节点

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

而后我以为一个很关键的地方是: tryObjectEncoding函数,下面开始分析这个函数。

PS:看这个函数以前,我特地翻阅了processInlineBuffer代码,里面有这么一行:

c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);

也就是说,目前的参数的形式都是: REDIS_STRING.

好,下面能够正式分析tryObjectEncoding

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

long value;
//设置一个long类型变量

sds s = o->ptr;
//指向一个字符串

size_t len;
//设置一个size_t类型的变量


if (o->encoding != REDIS_ENCODING_RAW)
return o; /* Already encoded */
//若是encoding方式是REDIS_ENCODING_RAW,则不用编码
//显然上面会执行,由于刚进来都是REDIS_STRING

那只好直接返回了

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

如今回到setCommand函数。

最后一行代码:setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);

那就进入setGenericCommand函数来看看本质吧。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

long long milliseconds = 0; /* initialized to avoid any harmness warning */
//初始化为 0
//自定义检查点: 1 2 3

if (expire) 
{


//若是设置了时间

if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
return;
//提取参数放到milliseconds里去

if (milliseconds <= 0) 
{
addReplyError(c,"invalid expire time in SETEX");
return;
}
//对有效性进行验证
//自定义检查点: 1  2 3


if (unit == UNIT_SECONDS) 
milliseconds *= 1000;
//若是是秒,还要换算成毫秒

}

~~~~~~~~~~~

if (
(flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL)
||
(flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL)
)
{
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
return;
}
判断有效性

~~~~~~~~~~

下面是最关键的函数setKey(c->db,key,val);

开始看这个函数。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

这个函数调用了lookupKeyWrite(db,key)。

而后lookuKeyWrite又调用了lookupKey。

因此我只好去看lookupKey函数。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

dictEntry *de = dictFind(db->dict,key->ptr);//去哈希表里找出这个节点

if (de) 
{
//若是节点存在
robj *val = dictGetVal(de);
//取出value

if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
val->lru = server.lruclock;
//更新访问时间

return val;
} else {
return NULL;
}
//返回当前值或者NULL
lookupKey函数结束,返回到函数lookupKeyWrite。

而后lookupKeyWrite函数也结束了,

回到setKey函数中来继续执行。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if (lookupKeyWrite(db,key) == NULL) 
{
//若是找不到,则增长
增长的代码是: dbAdd(db,key,val);

~~~~~~~~~研究dbAdd函数。

dbAdd调用dictAdd,dictAdd调用dictAddRaw,

这样就把一个(key,value)加入到了哈希表中去。

获得这个结果然爽!

~~~~~~~~~~~~~~~~~~~~~~

若是已经存在,则调用dbOverwrite函数。

这个是经过dictReplace函数实现。

具体的就不分析了

~~~~~~~~~~~~~~~~~~~~~~

incrRefCount实现增长引用次数。

~~~~~~~

其它的暂时不深刻。

我以为不少东西不是一次性弄懂的,须要循环的按部就班方式来整理才能够。

学习不是一蹴而就的东西!

~~~~~~~~~~~

既然setKey函数结束了,咱们回到setGenericCommand函数中。

server.dirty++;
//表示有多少个脏数据

if (expire) 
{
//若是设置了超时时间,则设置超时时间到节点里去
//单位:毫秒
setExpire(c->db,key,mstime()+milliseconds);
}
setExpire函数颇有意思,

是先从dict哈希表里找出节点,

而后添加/查找到一样key的节点,设置s64变量为超时的毫秒。

union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

具体参见上面的数据结构。

~~~~~~~~~~~~

个人疑问:为何要放一份只带key的数据到expires哈希表中去呢?

直接在dict表中设置也能够。或许是考虑到竞争使用的问题?

~~~~~~~~~~~~~~~~~~~

setExpire函数就此结束。

回到setGenericCommand函数继续执行。

notifyKeyspaceEvent暂且不考虑。

最后天然是发送"+OK\r\n"返回给客户了。

setGenericCommand就此结束,返回到setCommand,

setCommand就此结束。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

总结:

client:  SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */

server: "+OK\r\n"

对Redis的源码分析系列暂时到此为止!后续会不按期更新,敬请关注!

文章若转载,必须添加本文原始连接。

版权全部,侵权必究!

                            ————强子哥哥  837500869

相关文章
相关标签/搜索