簡介
REDIS有非常豐富的數(shù)據(jù)結(jié)構(gòu) 以及建立在這數(shù)據(jù)結(jié)構(gòu)上的操作,在源文件中主要集中在 T_hash.c /T_list.c /T_string.c/T_zset.c
可以說讀懂了這4個源文件 大部分?jǐn)?shù)據(jù)結(jié)構(gòu)命令都比較清楚了。 先從T_string.c源文件開始讀起:
T_string.c SET命令
命令簡介
SET key value [EX seconds] [PX milliseconds] [NX|XX] 1)設(shè)置了Key Value的時間 有2種單位: 第一: EX 對應(yīng)的是秒 第2: PX 是毫秒
命令源碼分解
- void setCommand(redisClient *c) {
- int j;
- robj *expire = NULL;
- int unit = UNIT_SECONDS;
- int flags = REDIS_SET_NO_FLAGS;
- for (j = 3; j < c->argc; j++) {
- char *a = c->argv[j]->ptr;
- robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
- if ((a[0] == 'n' || a[0] == 'N') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
- flags |= REDIS_SET_NX;
- } else if ((a[0] == 'x' || a[0] == 'X') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
- flags |= REDIS_SET_XX;
- } else if ((a[0] == 'e' || a[0] == 'E') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
- unit = UNIT_SECONDS;
- expire = next;
- j++;
- } else if ((a[0] == 'p' || a[0] == 'P') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
- unit = UNIT_MILLISECONDS;
- expire = next;
- j++;
- } else {
- addReply(c,shared.syntaxerr);
- return;
- }
- }
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
- }
解析:line 8: a獲取每個分割的命令參數(shù)的頭指針 j從3開始, 如果命令如:
set key value 這種 中間的循環(huán)就不會做了, line 3-5變量的設(shè)置就是原始值
如果做了中間的循環(huán):
首先由2種可能: ex 和Px 但是在寫代碼的時候 他就會注意到, 后面還會不會接其他的命令
最終就解析出來了 line33主要是防止Object Value是一個數(shù)字 看看能不能進行重新編碼
Line 34:就調(diào)用通用的SetCommand。 這里實現(xiàn)了所有SET命令版本的入口
- void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
- long long milliseconds = 0; /* initialized to avoid any harmness warning */
- if (expire) {
- if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
- return;
- if (milliseconds <= 0) {
- addReplyError(c,"invalid expire time in SETEX");
- return;
- }
- 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);
- server.dirty++;
- if (expire) setExpire(c->db,key,mstime()+milliseconds);
- addReply(c, ok_reply ? ok_reply : shared.ok);
line 4到line 11 :是計算出需要多長時間
line 14- 15: lookupKeyWrite(c->db,key) != NULL) 查看是否有這個key 如果沒有就寫入
同樣的:lookupKeyWrite(c->db,key) == NULL) 查看是否有這個key 如果有就寫入
line 20: 調(diào)用db.c/setKey函數(shù),就把相應(yīng)的Key和value插入到dict[]中去了
line 21: dirty重新賦值
line22:調(diào)用db.c/setExpire函數(shù) 把時間鍵值插入到這個expire字典里了。 mstime()是計算出當(dāng)前時間的長整形秒數(shù)。 至此 整個SET命令完成了~!
T_string.c GET命令 難度【*】
- int getGenericCommand(redisClient *c) {
- robj *o;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
- return REDIS_OK;
- if (o->type != REDIS_STRING) {
- addReply(c,shared.wrongtypeerr);
- return REDIS_ERR;
- } else {
- addReplyBulk(c,o);
- return REDIS_OK;
- }
- }
get命名從Line 1 進入: 調(diào)用DB.C里的lookupKeyReadOrReply函數(shù) 如果不存在 直接返回OK。
如果不是REDIS_STRING類型: 則返回一個錯誤。
如果完全正確 Line 11:把結(jié)果返回給客戶端
T_string.c GETSET/INCR命令 難度【*】
這2個命令非常的實用
- void getsetCommand(redisClient *c) {
- if (getGenericCommand(c) == REDIS_ERR) return;
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- setKey(c->db,c->argv[1],c->argv[2]);
- server.dirty++;
- }
這里可以看到 Line 2: 先調(diào)用get命令
然后line 4:進行setKey 覆蓋掉以前的old key
這個命令可以完成那種復(fù)位計數(shù)器的功能:獲得當(dāng)前某個變量值 然后置空
INCR命令 難度【**】
對key 存儲的數(shù)字加1 當(dāng)然他的key必須要存儲的是數(shù)字哦
- void incrDecrCommand(redisClient *c, long long incr) {
- long long value, oldvalue;
- robj *o, *new;
- o = lookupKeyWrite(c->db,c->argv[1]);
- if (o != NULL && checkType(c,o,REDIS_STRING)) return;
- if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
- oldvalue = value;
- if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
- (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
- addReplyError(c,"increment or decrement would overflow");
- return;
- }
- value += incr;
- new = createStringObjectFromLongLong(value);
- if (o)
- dbOverwrite(c->db,c->argv[1],new);
- else
- dbAdd(c->db,c->argv[1],new);
- signalModifiedKey(c->db,c->argv[1]);
- server.dirty++;
- addReply(c,shared.colon);
- addReply(c,new);
- addReply(c,shared.crlf);
- }
- void incrCommand(redisClient *c) {
- incrDecrCommand(c,1);
- }
line28:命令入口 然后進入Line1:
line 5: 先找到key對應(yīng)的value 先判斷是不是STRING 是撤回, 然后判斷能否轉(zhuǎn)換成Longlong型數(shù) 如果能轉(zhuǎn)換 就繼續(xù)下面的工作: 如果遞增 會導(dǎo)致數(shù)量越界 就返回。
修改了具體的數(shù)值
注意點: 如果o為NULL,則沒有這個key 那么就會采用這個方案:dbAdd,如果含有 則改寫
最后一個命令:line 21: 需要通知下 相關(guān)的被watch的Key, 如果被watched的key里有這個key 則那個key的相應(yīng)標(biāo)記位需要置為臟。 但不影響這里的操作
題外話 : 引入INCR的原因
試想 如果N客戶端 都想對某個變量做自增的一個操作 如果沒有INCR的話 只能是取回 在本地加一 然后在傳上去 但是這樣必須進行原子鎖 所以是不行的 如果這樣的事情交給服務(wù)器做,就可以避免這樣的問題,對于客戶端來講只需要提供INCR命令,剩下的都是redis 進行流水線操作 就絕對能保持其自增效果 對于這個INCR在我們實驗室的爬蟲部分 關(guān)于多臺主機爬取信息 怎么根據(jù)ID自增來插入表格 是一個非常好的解決方案~!
T_string.c APPEND命令 難度【**】
如果已經(jīng)存在 就講value加到key的末尾 如果不存在 就是簡單的set key value
- void appendCommand(redisClient *c) {
- size_t totlen;
- robj *o, *append;
- o = lookupKeyWrite(c->db,c->argv[1]);
- if (o == NULL) {
- /* Create the key */
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- dbAdd(c->db,c->argv[1],c->argv[2]);
- incrRefCount(c->argv[2]);
- totlen = stringObjectLen(c->argv[2]);
- } else {
- /* Key exists, check type */
- if (checkType(c,o,REDIS_STRING))
- return;
- /* "append" is an argument, so always an sds */
- append = c->argv[2];
- totlen = stringObjectLen(o)+sdslen(append->ptr);
- if (checkStringLength(c,totlen) != REDIS_OK)
- return;
- /* If the object is shared or encoded, we have to make a copy */
- if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
- robj *decoded = getDecodedObject(o);
- o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
- decrRefCount(decoded);
- dbOverwrite(c->db,c->argv[1],o);
- }
- /* Append the value */
- o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
- totlen = sdslen(o->ptr);
- }
- signalModifiedKey(c->db,c->argv[1]);
- server.dirty++;
- addReplyLongLong(c,totlen);
- }
分2種: 如果沒有含key 就直接加入
如果沒有含key 則重新分配
line 32:sdscatlen() 將o->ptr的內(nèi)容分配到append->ptr里。
T_string.c MSET/MGET命令
簡介:
一次性進行多次插入和取操作命令 是一個原子操作
void msetGenericCommand(redisClient *c, int nx) {
- int j, busykeys = 0;
- if ((c->argc % 2) == 0) {
- addReplyError(c,"wrong number of arguments for MSET");
- return;
- }
- /* Handle the NX flag. The MSETNX semantic is to return zero and don't
- * set nothing at all if at least one already key exists. */
- if (nx) {
- for (j = 1; j < c->argc; j += 2) {
- if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
- busykeys++;
- }
- }
- if (busykeys) {
- addReply(c, shared.czero);
- return;
- }
- }
- for (j = 1; j < c->argc; j += 2) {
- c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
- setKey(c->db,c->argv[j],c->argv[j+1]);
- }
- server.dirty += (c->argc-1)/2;
- addReply(c, nx ? shared.cone : shared.ok);
- }
- void msetCommand(redisClient *c) {
- msetGenericCommand(c,0);
- }
同樣 入口是line 29 很簡單:
進入line msetGenericCommand之后, Line 3 先看下是不是有參數(shù)錯了, 如果所有參數(shù)和是個偶數(shù) 就證明錯了
line 9-14: 如果是msetnx就是key不存在的時候能插入 如果存在不讓插入 nx=1的話 就是調(diào)用msetnx命令
注意點:這里是原子操作,要么全面插入成功 要么全部失敗,也就是說:如果中間有一個key存在 msetnx直接從17返回了
Line21-26:這里是真正的插入信息 每2個作為一組進行插入 然后就講dirty更新
MGET命令基本上是一樣的原理 再次忽略
t_string.c 基本上就是這些命令 ,但是奇怪的是getbit setbit的源碼不在,這個bit 2進制數(shù)組需要在其他的文件中應(yīng)該會出現(xiàn),出現(xiàn)在其他源文件中放在其他文件中分析。
下篇預(yù)告: List操作 這個是實驗室項目用的最多的,所以必須要很好的分析。
浙公網(wǎng)安備 33010602011771號