男人的av一区二区资源,亚洲日韩国产精品无码av,蜜桃久久久aaaa成人网一区,亚洲日韩中文字幕一区,在线观看国产亚洲视频免费

【Redis源碼】Redis Set命令詳解

a3c7e433ab54bcf9f65b4042a058cace.jpg

簡(jiǎn)介

set命令用于將key-value設置到數據庫。如果key已經(jīng)設置,則set會(huì )用新值覆蓋舊值,不管原value是何種類(lèi)型,如果在設置時(shí)不指定EX或PX參數,set命令會(huì )清除原有超時(shí)時(shí)間。

格式:

SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>]

參數:

  • NX: 當數據庫中key不存在時(shí),可以將key-value添加到數據
    庫。
  • XX: 當數據庫中key存在時(shí),可以將key-value設置到數據庫,
    與NX參數互斥。
  • EX: key的超時(shí)秒數。
  • PX: key的超時(shí)毫秒數,與EX參數互斥。

命令行解析額外參數

set命令共支持NX、XX、EX、PX這4個(gè)額外參數,在執行set命令時(shí),需要首先對這4個(gè)參數進(jìn)行解析,此時(shí)需要3個(gè)局部變量來(lái)輔助實(shí)現:

robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = OBJ_SET_NO_FLAGS;

expire:超時(shí)時(shí)間,robj類(lèi)型。我們知道,Redis在解析命令行參數時(shí),會(huì )將各個(gè)參數解析成robj類(lèi)型,當expire值不為NULL則表示需要設置key的超時(shí)時(shí)間。

unit:字符串的超時(shí)時(shí)間單位有秒和毫秒兩種,程序中根據此值來(lái)確認超時(shí)的單位,此值只有兩個(gè)取值,分別為:

#define UNIT_SECONDS 0 //單位:秒
#define UNIT_MILLISECONDS 1 //單位:毫秒

flags:int類(lèi)型,它是一個(gè)二進(jìn)制串,程序中根據此值來(lái)確定key是否應該被設置到數據庫。它由下列5個(gè)值來(lái)表示不同的含義:

#define OBJ_SET_NO_FLAGS 0
#define OBJ_SET_NX (1<<0) //標識key沒(méi)有被設置過(guò)
#define OBJ_SET_XX (1<<1) //標識key已經(jīng)存在
#define OBJ_SET_EX (1<<2) //標識key的超時(shí)時(shí)間被設置為單位秒
#define OBJ_SET_PX (1<<3) //標識key的超時(shí)時(shí)間被設置為單位毫秒

在知道了這3個(gè)變量的意義之后,再來(lái)看解析參數的具體過(guò)程。由set命令的參數格式得知,前3個(gè)參數為set、key、value,這3個(gè)參數是通用參數,我們暫時(shí)先不考慮,先從第4個(gè)參數開(kāi)始依次向后通過(guò)
for循環(huán)解析:

for (j = 3; j < c->argc; j++) {
char *a = c->argv[j]->ptr;
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];

*a表示遍歷參數時(shí)遇到的參數字符串;*next表示當前遍歷參數的下個(gè)參數,如果當前遍歷到最后一個(gè)參數時(shí),*next的值為NULL。

如果遇到參數NX(不區分大小寫(xiě)),并且沒(méi)有設置過(guò)OBJ_SET_XX,表示key在沒(méi)有被設置過(guò)的情況下才可以被設置,flags賦值如下。

flags |= OBJ_SET_NX;

如果遇到參數XX(不區分大小寫(xiě)),并且沒(méi)有設置這OBJ_SET_NX時(shí),表示key在已經(jīng)被設置的情況下才可以被設置,flags賦值如下。

flags |= OBJ_SET_XX;

如果遇到參數EX(不區分大小寫(xiě)),并且沒(méi)有設置過(guò)OBJ_SET_PX,且下個(gè)參數存在,表示key的過(guò)期時(shí)間單位為秒,秒數由下個(gè)參數指定。

flags |= OBJ_SET_EX;
unit = UNIT_SECONDS;
expire = next;
j++;

設置過(guò)期時(shí)間時(shí),由EX和時(shí)間兩個(gè)參數共同確定,所以EX的下個(gè)參數肯定為秒數值,所以直接跳過(guò)下個(gè)參數的循環(huán),j++。

如果遇到參數PX(不區分大小寫(xiě)),并且沒(méi)有設置過(guò)OBJ_SET_EX,且下個(gè)參數存在。表示key的過(guò)期時(shí)間單位為毫秒,毫秒數由下個(gè)參數指定。

flags |= OBJ_SET_PX;
unit = UNIT_MILLISECONDS;
expire = next;
j++;

設置過(guò)期毫秒時(shí),由PX和時(shí)間兩個(gè)參數共同確定,所以PX的下個(gè)參數肯定為毫秒值,所以直接跳到下個(gè)參數的循環(huán),j++。

value編碼

為了節省空間,在將key-value設置到數據庫之前,根據value的不同長(cháng)度和類(lèi)型對value進(jìn)行編碼。編碼的函數為:

robj *tryObjectEncoding(robj *o)

該函數執行過(guò)程經(jīng)過(guò)如下幾步。

判斷o的類(lèi)型是否為string類(lèi)型。如果不為string類(lèi)型則不能對robj類(lèi)型進(jìn)行操作:

serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

判斷o的encoding是否為sds類(lèi)型,只有sds類(lèi)型的數據才可以進(jìn)一步優(yōu)化:

if (!sdsEncodedObject(o)) return o;

sdsEncodedObject的定義如下:

#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)

此時(shí)的encoding為OBJ_ENCODING_EMBSTR,所以此時(shí)是滿(mǎn)足條件的。

判斷引用計數refcount,如果對象的引用計數大于1,表示此對象在多處被引用。在tryObjectEncoding函數結束時(shí)可能會(huì )修改o的值,所以貿然繼續進(jìn)行可能會(huì )造成其他影響,所以在refcount大于1的情況下,結束函數的運行,將o直接返回:

if (o->refcount > 1) return o;

求value的字符串長(cháng)度,當長(cháng)度小于等于20時(shí),試圖將value轉化為long類(lèi)型,如果轉換成功,則分為兩種情況處理:

if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
}

其中MAXMEMORY_FLAG_NO_SHARED_INTEGERS和OBJ_SHARED_INTEGERS的定義如下:

#define MAXMEMORY_FLAG_NO_SHARED_INTEGERS \
(MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU)
#define OBJ_SHARED_INTEGERS 10000

第一種情況: 如果Redis的配置不要求運行LRU或LFU替換算法,并且轉換后的value值小于OBJ_SHARED_INTEGERS,那么會(huì )返回共享數字對象。之所以這里的判斷跟替換算法有關(guān),是因為替換算法要求每個(gè)robj有不同的lru字段值,所以用了替換算法就不能共享robj了。通過(guò)上一章我們知道shared.integers是一個(gè)長(cháng)度為10000的數組,里面預存了10000個(gè)數字對象,從0到9999。這些對象都是encoding=OBJ_ENCODING_INT的robj對象。

第二種情況: 如果不能返回共享對象,那么將原來(lái)的robj的encoding改為OBJ_ENCODING_INT,這時(shí)robj的ptr字段直接存儲為這個(gè)long型的值。robj的ptr字段本來(lái)是一個(gè)void*指針,所以在64位機器占8字節的長(cháng)度,而一個(gè)long也是8字節,所以不論ptr存一個(gè)指針地址還是一個(gè)long型的值,都不會(huì )有額外的內存開(kāi)銷(xiāo)。

對于那些不能轉成64位long的字符串最后再做兩步處理:

if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb;
if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
emb = createEmbeddedStringObject(s,sdslen(s));
decrRefCount(o);
return emb;
}
if (o->encoding == OBJ_ENCODING_RAW &&
sdsavail(s) > len/10)
{
o->ptr = sdsRemoveFreeSpace(o->ptr);
}
  1. 如果字符串長(cháng)度小于等于OBJ_ENCODING_EMBSTR_SIZE_LIMIT,定義為44,那么調用
    createEmbeddedStringObject將encoding改為OBJ_ENCODING_EMBSTR;
  2. 如果前面所有的編碼嘗試都沒(méi)有成功,此時(shí)仍然是OBJ_ENCODING_RAW類(lèi)型,且sds里空余字節過(guò)多,那么就會(huì )調用sds的sdsRemoveFreeSpace接口來(lái)釋放空余字節。通過(guò)以上5個(gè)步驟,我們來(lái)看一下set key1100現在的第2個(gè)參數的

數據庫添加key-value

當將value值優(yōu)化好之后,調用setGenericCommand函數將keyvalue設置到數據庫。

set命令調用setGenericCommand傳遞的參數如下:

setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);

setGenericCommand的函數定義如下:

void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) ;

此時(shí)需要根據之前所賦值的flags來(lái)確定現在是否可以將key-value設置成功。

if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))

當有OBJ_SET_NX標識時(shí),需要保證當前數據庫中沒(méi)有key值。當有OBJ_SET_XX時(shí),需要保證當前數據庫中已經(jīng)有key值。否則直接報錯退出。
當判斷當前key-value可以寫(xiě)入數據庫之后,調用setKey方法將key-value寫(xiě)入數據庫。

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);
....
}

setKey方法調用dbAdd或dbOverwrite方法來(lái)寫(xiě)入key-value,依據當前數據庫中是否有key來(lái)決定采用哪個(gè)函數來(lái)寫(xiě)入。數據的寫(xiě)入實(shí)際是將key-value寫(xiě)入了redisDb的dict內,字典在之前介紹過(guò),在此不再贅述。注意在寫(xiě)入key-value時(shí),不管之前這個(gè)key是否設置為超時(shí)時(shí)間,這里將該key的超時(shí)時(shí)間移除。

設置超時(shí)時(shí)間

將key-value設置到數據庫之后,如果命令行參數里指定了超時(shí)時(shí)間,那么就需要設置key的超時(shí)時(shí)間。當然在設置超時(shí)時(shí)間之前需要判斷時(shí)間值是否為long類(lèi)型。Redis key的超時(shí)時(shí)間實(shí)際存儲的是當前key的到期毫秒時(shí)間戳,所以在指定超時(shí)時(shí)間單位為秒時(shí),需要將時(shí)間值乘以1000來(lái)轉化為毫秒數,將當前時(shí)間加上超時(shí)毫秒數的結果就是key的超時(shí)毫秒時(shí)間戳。

Redis將所有含有超時(shí)時(shí)間的key存儲到redisDb的expire字典內,ttl命令可以快速確定key的超時(shí)秒數,就是通過(guò)查找這個(gè)字典實(shí)現的。

通過(guò)以上4個(gè)步驟已經(jīng)成功地將一個(gè)key-value設置到Redis的數據庫中。



標 題:《【Redis源碼】Redis Set命令詳解
作 者:zeekling
提 示:轉載請注明文章轉載自個(gè)人博客:浪浪山旁那個(gè)村

    評論
    0 評論
avatar

取消
男人的av一区二区资源,亚洲日韩国产精品无码av,蜜桃久久久aaaa成人网一区,亚洲日韩中文字幕一区,在线观看国产亚洲视频免费