這個命令應該是打通不少命令的關鍵點,估計也是用的最多的命令了。 數據結構
因此必須一字一句的來剖析這個命令的本質! 函數
我只能說:全部的反動派都是紙老虎! 源碼分析
注意:這個操做的數據影響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