这个命令应该是打通不少命令的关键点,估计也是用的最多的命令了。 数据结构
因此必须一字一句的来剖析这个命令的本质! 函数
我只能说:全部的反动派都是纸老虎! 源码分析
注意:这个操做的数据影响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