1. 程式人生 > 其它 >redis6.0.5之t_string閱讀筆記--字串鍵1

redis6.0.5之t_string閱讀筆記--字串鍵1

/*-----------------------------------------------------------------------------
 * String Commands  字串命令
 *----------------------------------------------------------------------------*/
檢查字元長度,超過512MB 判定錯誤
static int checkStringLength(client *c, long long size) {
    if (size > 512*1024*1024) {
        addReplyError(c,
"string exceeds maximum allowed size (512MB)"); return C_ERR; } return C_OK; } ********************************************************************************************************************* /* The setGenericCommand() function implements the SET operation with different * options and variants. This function is called in order to implement the * following commands: SET, SETEX, PSETEX, SETNX. setGenericCommand這個函式 用不同的選項和變數實現了SET命令。 這個函式被呼叫用來實現以下的命令: SET, SETEX, PSETEX, SETNX. * 'flags' changes the behavior of the command (NX or XX, see below). 變數flags改變了命令的表現(NX 或者 XX, 看下面的介紹) * 'expire' represents an expire to set in form of a Redis object as passed * by the user. It is interpreted according to the specified 'unit'. 變數expire 表示一個由使用者傳入的redis物件的過期設定 。這個過期設定的解釋需要根據特定的單位(unit 有秒和毫秒的差別) * 'ok_reply' and 'abort_reply' is what the function will reply to the client * if the operation is performed, or when it is not because of NX or * XX flags. ok_reply和abort_reply 是函式執行之後回覆客戶端的訊息,或者是因為標誌NX或XX標誌沒有執行的回覆 * If ok_reply is NULL "+OK" is used. 如果ok_reply是空的,那麼+OK被使用 * If abort_reply is NULL, "$-1" is used.
*/ 如果abort_reply是空的,那麼$-1被使用 #define OBJ_SET_NO_FLAGS 0 #define OBJ_SET_NX (1<<0) /* Set if key not exists. */ 設定如果鍵不存在 #define OBJ_SET_XX (1<<1) /* Set if key exists. */ 設定如果鍵存在 #define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */ 按秒設定時間如果有傳入 #define
OBJ_SET_PX (1<<3) /* Set if time in ms in given */ 按毫秒設定時間如果有傳入 #define OBJ_SET_KEEPTTL (1<<4) /* Set and keep the ttl */ 設定和儲存TTL //robj結構體的定義 typedef struct redisObject { unsigned type:4; 型別 unsigned encoding:4; 編碼方式 unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ 淘汰策略 int refcount; 引用計數 void *ptr; 指向物件 } robj; void setGenericCommand(client *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) != C_OK) 獲取redis物件的超時設定時間 return; if (milliseconds <= 0) { 設定的超時時間小於等於0,是無效的過期時間 addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); return; } if (unit == UNIT_SECONDS) milliseconds *= 1000; 如果單位是秒,那麼需要乘以1000 } if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) || 如果設定了鍵不存在的標誌但實際庫中鍵存在 (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL)) 如果設定了鍵存在的標誌但是實際庫中鍵不存在 { 這兩種情況下都不需要實際實行命令,返回丟棄資訊即可 addReply(c, abort_reply ? abort_reply : shared.null[c->resp]); return; } genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1); 設定庫中鍵對應的值 server.dirty++; 修改次數 if (expire) setExpire(c,c->db,key,mstime()+milliseconds); 如果設定超時,那麼就需要將對應鍵的值設定時間 notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id); 對訂閱了事件set的客戶端進行通知 if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC, "expire",key,c->db->id); 設定了超期標誌,通知訂閱客戶端事件expire addReply(c, ok_reply ? ok_reply : shared.ok); 回覆執行命令成功 } ********************************************************************************************************************* /* SET key value [NX] [XX] [KEEPTTL] [EX <seconds>] [PX <milliseconds>] */ 命令的格式如上 set 鍵 值 後面都時可選的項 NX 不存在 XX存在 KEEPTTL保留設定前指定的過期時間 EX單位按秒 PX單位按毫秒 void setCommand(client *c) { int j; robj *expire = NULL; int unit = UNIT_SECONDS; int flags = OBJ_SET_NO_FLAGS; 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]; 是不是最後一個引數,不是的話next就指向下個引數 if ((a[0] == 'n' || a[0] == 'N') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && !(flags & OBJ_SET_XX)) { flags |= OBJ_SET_NX; 設定不存在標誌 } else if ((a[0] == 'x' || a[0] == 'X') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && !(flags & OBJ_SET_NX)) { flags |= OBJ_SET_XX; 設定存在標誌 } else if (!strcasecmp(c->argv[j]->ptr,"KEEPTTL") && 如果引數為KEEPTTL(不區分大小寫) !(flags & OBJ_SET_EX) && !(flags & OBJ_SET_PX)) 並且引數沒有設定時間單位 { flags |= OBJ_SET_KEEPTTL; 設定儲存設定前的超期時間值 } else if ((a[0] == 'e' || a[0] == 'E') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && !(flags & OBJ_SET_KEEPTTL) && !(flags & OBJ_SET_PX) && next) 存在下一個引數 { flags |= OBJ_SET_EX; unit = UNIT_SECONDS;按秒為單位計算 expire = next; 下個值就是超時的值 j++;下一個引數 } else if ((a[0] == 'p' || a[0] == 'P') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && !(flags & OBJ_SET_KEEPTTL) && !(flags & OBJ_SET_EX) && next) { flags |= OBJ_SET_PX; 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); 設定具體的值 } ********************************************************************************************************************* 設定不存在鍵的值 void setnxCommand(client *c) { c->argv[2] = tryObjectEncoding(c->argv[2]); setGenericCommand(c,OBJ_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero); } 按妙設定超時 void setexCommand(client *c) { c->argv[3] = tryObjectEncoding(c->argv[3]); setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL); } 按毫秒設定超時 void psetexCommand(client *c) { c->argv[3] = tryObjectEncoding(c->argv[3]); setGenericCommand(c,OBJ_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL); } ********************************************************************************************************************* 獲取庫中的鍵值 int getGenericCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL) 不存在返回OK return C_OK; if (o->type != OBJ_STRING) { 存在但是型別不是字串 addReply(c,shared.wrongtypeerr); 返回錯誤的型別 return C_ERR; } else { addReplyBulk(c,o); 按照特定分塊資料格式返回 return C_OK; } } ********************************************************************************************************************* void getCommand(client *c) { getGenericCommand(c); } ********************************************************************************************************************* void getsetCommand(client *c) { if (getGenericCommand(c) == C_ERR) return; 從庫中獲取值 c->argv[2] = tryObjectEncoding(c->argv[2]); 嘗試對字串編碼從而節省空間 setKey(c,c->db,c->argv[1],c->argv[2]); 設定鍵對應的值 notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[1],c->db->id); 通知訂閱客戶端set事件 server.dirty++; 自從上次儲存庫之後鍵改變的數量加1 } ********************************************************************************************************************* void setrangeCommand(client *c) { robj *o; long offset; sds value = c->argv[3]->ptr; if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK) return; if (offset < 0) { addReplyError(c,"offset is out of range"); return; } o = lookupKeyWrite(c->db,c->argv[1]); if (o == NULL) { 鍵不在資料庫中 /* Return 0 when setting nothing on a non-existing string */ 返回0如果在不存在的字串上設定空串 if (sdslen(value) == 0) { addReply(c,shared.czero); return; } /* Return when the resulting string exceeds allowed size */ 當結果字串長度超過最大的允許長度時直接返回 if (checkStringLength(c,offset+sdslen(value)) != C_OK) return; o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value))); 建立新的值物件 dbAdd(c->db,c->argv[1],o); 新增到對應的資料庫中 } else { size_t olen; /* Key exists, check type */ if (checkType(c,o,OBJ_STRING)) 不是字串型別,直接返回 return; /* Return existing string length when setting nothing */ olen = stringObjectLen(o); 當什麼也不設定時,返回存在的字串長度 if (sdslen(value) == 0) { 新值的長度為0,返回原值的長度 addReplyLongLong(c,olen); return; } /* Return when the resulting string exceeds allowed size */ 當結果字串超過了允許的長度,直接返回 if (checkStringLength(c,offset+sdslen(value)) != C_OK) return; /* Create a copy when the object is shared or encoded. */ 建立一份拷貝,如果這個物件是共享或者編碼的 o = dbUnshareStringValue(c->db,c->argv[1],o); } if (sdslen(value) > 0) { 如果新值長度大於0 o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); 縮減不必要的長度 memcpy((char*)o->ptr+offset,value,sdslen(value)); 拼接字串 signalModifiedKey(c,c->db,c->argv[1]);通知鍵被修改的資訊 notifyKeyspaceEvent(NOTIFY_STRING, "setrange",c->argv[1],c->db->id); 通知事件setrange server.dirty++; 上次庫儲存之後變動的鍵加1 } addReplyLongLong(c,sdslen(o->ptr));回覆客戶端 } ********************************************************************************************************************* 獲取一個鍵對應值的區間段, 比如設定 set ccy "this is a test" getrange ccy 1 3 結果為 his void getrangeCommand(client *c) { robj *o; long long start, end; 傳入的開始和結尾 char *str, llbuf[32]; size_t strlen; if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) 獲取開始位置的值 return; if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK) 獲取最後一個引數,即結束位置的值 return; 通過傳入的引數鍵獲取對應的值 或者 檢查值是否為字串 當值為空或者不是字串的時候,就返回,表示沒有區間可以獲得 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL || checkType(c,o,OBJ_STRING)) return; if (o->encoding == OBJ_ENCODING_INT) { str = llbuf; strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); 將數字裝換為字串 } else { 字串編碼,直接使用 str = o->ptr; strlen = sdslen(str); } /* Convert negative indexes */ 轉化負索引的情況,就是反向索引 if (start < 0 && end < 0 && start > end) { 如果開始位置和結束位置都時負的,但是開始位置在結束位置後面,那麼結果也是為空 addReply(c,shared.emptybulk); return; } if (start < 0) start = strlen+start; 根據負索引的位置重新確定開始位置 if (end < 0) end = strlen+end; 結束位置 if (start < 0) start = 0; 如果還是小於0,那麼從0開始 if (end < 0) end = 0; if ((unsigned long long)end >= strlen) end = strlen-1; 如果超出長度,那麼定位到最後一個字元 /* Precondition: end >= 0 && end < strlen, so the only condition where * nothing can be returned is: start > end. */ 前提條件:end>=0&&end<strlen,因此在這種情況下,不能返回任何內容的唯一條件是:start>end if (start > end || strlen == 0) { addReply(c,shared.emptybulk); } else { addReplyBulkCBuffer(c,(char*)str+start,end-start+1); 否則就返回正常的區間段 } } ********************************************************************************************************************* void mgetCommand(client *c) { int j; addReplyArrayLen(c,c->argc-1); 確定回覆的個數 for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); 對每個鍵進行查詢, if (o == NULL) { 不存在就返回空 addReplyNull(c); } else { if (o->type != OBJ_STRING) { addReplyNull(c); } else { addReplyBulk(c,o); 存在而且型別是字串就返回值 } } } } ********************************************************************************************************************* void msetGenericCommand(client *c, int nx) { int j; 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 anything if at least one key alerady exists. */ 當標誌為NX時,只要有一個鍵存在,命令MSETNX就不設定其它所有的值,返回0 if (nx) { 當nx標誌存在時,檢查所有的鍵是否存在資料庫中 for (j = 1; j < c->argc; j += 2) { if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { addReply(c, shared.czero); return; } } } for (j = 1; j < c->argc; j += 2) { c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); 嘗試編碼縮小儲存空間 setKey(c,c->db,c->argv[j],c->argv[j+1]); 設定鍵的新值 notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id); } server.dirty += (c->argc-1)/2; 自從上次資料儲存之後變動的鍵值數目 addReply(c, nx ? shared.cone : shared.ok); } ********************************************************************************************************************* void msetCommand(client *c) { msetGenericCommand(c,0); } void msetnxCommand(client *c) { msetGenericCommand(c,1); } void incrDecrCommand(client *c, long long incr) { long long value, oldvalue; robj *o, *new; o = lookupKeyWrite(c->db,c->argv[1]); 在庫中查詢輸入的鍵 if (o != NULL && checkType(c,o,OBJ_STRING)) return; 如果非空並且不是字串,那麼直接返回。不能做加這個操作 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_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; 可以表示的情況下,就進行計算 if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT && (value < 0 || value >= OBJ_SHARED_INTEGERS) && value >= LONG_MIN && value <= LONG_MAX) { 非共享物件,不是小數值,沒有超過範圍,編碼是整型 那麼可以直接使用 new = o; o->ptr = (void*)((long)value); } else { 否則需要建立一個新的物件 new = createStringObjectFromLongLongForValue(value); 建立新的一個值 if (o) { dbOverwrite(c->db,c->argv[1],new); 存在就修改 } else { dbAdd(c->db,c->argv[1],new); 不存在就新增 } } signalModifiedKey(c,c->db,c->argv[1]); 給客戶端傳送鍵被修改的訊息 notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id); 對訂閱了訊息的客戶端傳送事件 server.dirty++; 變化過的鍵加1 addReply(c,shared.colon); 冒號 addReply(c,new); 內容 addReply(c,shared.crlf); 回車換行 } ********************************************************************************************************************* void incrCommand(client *c) { incrDecrCommand(c,1); 加數為1 } void decrCommand(client *c) { incrDecrCommand(c,-1); 同加法,只是加數為負數,-1 } void incrbyCommand(client *c) { long long incr; if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; incrDecrCommand(c,incr); 加數自定義 } void decrbyCommand(client *c) { long long incr; if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != C_OK) return; incrDecrCommand(c,-incr); 減數自定義 } ********************************************************************************************************************* 加一個浮點數 void incrbyfloatCommand(client *c) { long double incr, value; robj *o, *new, *aux1, *aux2; o = lookupKeyWrite(c->db,c->argv[1]); 資料庫查詢鍵對應的值 if (o != NULL && checkType(c,o,OBJ_STRING)) return; if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK || 本身的值,即鍵對應的資料庫中的值 getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK) 傳入的引數值 return; value += incr; if (isnan(value) || isinf(value)) { addReplyError(c,"increment would produce NaN or Infinity"); return; } new = createStringObjectFromLongDouble(value,1); if (o) dbOverwrite(c->db,c->argv[1],new); 非空,覆蓋 else dbAdd(c->db,c->argv[1],new); 空,新增 signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id); server.dirty++; addReplyBulk(c,new); /* Always replicate INCRBYFLOAT as a SET command with the final value * in order to make sure that differences in float precision or formatting * will not create differences in replicas or after an AOF restart. */ 始終將命令INCRBYFLOAT賦值成為具有最終set值的命令。 這是為了確保不同的操作中(複製或者在AOF重啟之後)浮點數精度和格式保持一致。 aux1 = createStringObject("SET",3); rewriteClientCommandArgument(c,0,aux1); decrRefCount(aux1); rewriteClientCommandArgument(c,2,new); aux2 = createStringObject("KEEPTTL",7); rewriteClientCommandArgument(c,3,aux2); decrRefCount(aux2); } ********************************************************************************************************************* 在已有的鍵對應的值後面新增新的字串或者不存在鍵的情況下,將字串作為值建立新鍵 void appendCommand(client *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,OBJ_STRING)) return; /* "append" is an argument, so always an sds */ append是一個引數,總是一個sds字串型別 append = c->argv[2]; totlen = stringObjectLen(o)+sdslen(append->ptr); 原字串長度+ 需要新增的字串長度 if (checkStringLength(c,totlen) != C_OK) 是否超過最大允許值 return; /* Append the value */ o = dbUnshareStringValue(c->db,c->argv[1],o); 獲取不共享的物件,可用於修改 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); 根據長度拼接字串 totlen = sdslen(o->ptr); 新字串長度 } signalModifiedKey(c,c->db,c->argv[1]); 通知修改過的key資訊給客戶端 notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id); 對訂閱了事件的客戶端傳送資訊鍵相關資訊 server.dirty++; 被修改的鍵加1 addReplyLongLong(c,totlen); 回覆客戶端總長度 } ********************************************************************************************************************* 返回鍵對應值的長度 void strlenCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_STRING)) return; addReplyLongLong(c,stringObjectLen(o)); 返回鍵對應值的長度 } *********************************************************************************************************************