baiyanredis
命令含義:判斷鍵是否存在。若是過時則不存在,不過時則存在
命令格式:segmentfault
EXISTS key1 key2 ... keyN
命令實戰:數組
127.0.0.1:6379> set key1 value1 OK 127.0.0.1:6379> exists key1 (integer) 1
返回值: 存在鍵的數量函數
exists命令對應的處理函數是existsCommand():源碼分析
void existsCommand(client *c) { long long count = 0; int j; for (j = 1; j < c->argc; j++) { // 去鍵空間字典尋找給定key是否存在,存在則count++ if (lookupKeyRead(c->db,c->argv[j])) count++; } // 返回找到key的數量 addReplyLongLong(c,count); }
一樣地,咱們使用gdb -p來觀察exists命令的執行過程,gdb的過程這裏再也不贅述。咱們在existsCommand處打一個斷點,而後在客戶端中執行命令:學習
127.0.0.1:6379> exists key1
觀察服務端,已經執行到咱們的斷點處了:ui
Breakpoint 1, existsCommand (c=0x7fb459ca3540) at db.c:496 496 void existsCommand(client *c) { (gdb) n 500 for (j = 1; j < c->argc; j++) { (gdb) 497 long long count = 0; (gdb) 501 if (lookupKeyRead(c->db,c->argv[j])) count++; (gdb) s
咱們看到,在入口函數existsCommand()中,遍歷了全部的命令參數,即咱們傳入的key1,這裏會調用lookupKeyRead()函數,去鍵空間中查找鍵是否過時,若是過時,則返回0,不存在。反之鍵存在,返回1。跟進這個函數調用:spa
lookupKeyRead (key=0x7fb462229ad0, db=0x7fb46221a800) at db.c:144 144 return lookupKeyReadWithFlags(db,key,LOOKUP_NONE); (gdb) s lookupKeyReadWithFlags (db=0x7fb46221a800, key=0x7fb462229ad0, flags=flags@entry=0) at db.c:100 100 robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { (gdb) n 103 if (expireIfNeeded(db,key) == 1) { (gdb) n 133 val = lookupKey(db,key,flags); (gdb) n
這個函數直接調用了lookupKeyReadWithFlags(),而後在lookupKeyReadWithFlags函數內部,調用了咱們熟悉的expireIfNeeded(),顯然在咱們調試過程當中,並無進這個if,由於咱們的鍵並無過時,因此確定返回0。因爲鍵並無過時,那麼在這裏應該直接返回了。可是因爲它是一個通用查找函數,還須要返回查找後的值,因此繼續調用lookupKey()函數,去鍵空間中查找key1對應的值value1。繼續往下執行:指針
133 val = lookupKey(db,key,flags); (gdb) n 134 if (val == NULL) (gdb) p val $1 = (robj *) 0x7fb46220e100 (gdb) p *val $2 = {type = 0, encoding = 8, lru = 8745377, refcount = 1, ptr = 0x7fb46220e113} 137 server.stat_keyspace_hits++; (gdb) 139 } (gdb) existsCommand (c=0x7fb459ca3540) at db.c:501 501 if (lookupKeyRead(c->db,c->argv[j])) count++;
咱們看到,在查找完成以後,會判斷是否爲NULL,顯然這裏不會爲NULL。打印val的值,是一個redisObj結構,這裏ptr指向的sds就是咱們的value1了。而後,redis會統計一個數據,是在鍵空間中查找到value的次數,這裏因爲咱們找到了,將其++,而後函數會將查找到的value1返回,最外層判斷不爲NULL,count++,命令執行結束。調試
在exists命令執行過程當中,一個核心的函數調用就是lookupKey()。這個函數查找一個鍵對應的value值。若是存在,返回該value值,不然返回NULL:
robj *lookupKey(redisDb *db, robj *key, int flags) { dictEntry *de = dictFind(db->dict,key->ptr); // 找到當前key-value對所在的dictEntry if (de) { robj *val = dictGetVal(de); // 是一個宏,直接返回dictEntry中的val字段 ... return val; } else { return NULL; } }
這個函數會首先調用dictFind(),直接返回這個key所在dict中的dictEntry。咱們跟進這個函數:
static dictEntry *dictFind(dict *ht, const void *key) { dictEntry *he; // dictEntry結構的指針 unsigned int h; // 存儲哈希值 if (ht->size == 0) return NULL; // 字典爲空,直接返回不存在 h = dictHashKey(ht, key) & ht->sizemask; // 計算哈希值 he = ht->table[h]; // 訪問字典對應哈希值位置上的元素,它是一個指針,指向dictEntry結構 // 遍歷鏈地址法解決衝突的鏈表 while(he) { if (dictCompareHashKeys(ht, key, he->key)) // 挨個比較當前dictEntry的key值是否等於咱們要找的key值 return he; // 找到了,直接返回 he = he->next; // 沒找到,繼續日後遍歷鏈表 } return NULL; // 遍歷到鏈表尾部尚未找到,說明沒有該元素 }
在說明該函數查找流程以前,咱們回顧一下字典的存儲結構:
代碼中的he = ht->table[h]訪問的就是咱們**table這個指針。它是一個二級指針,能夠當作一個一維數組,每一個數組中的元素都是一個dictEntry的指針。咱們訪問到了第h個(計算後的哈希值)元素的bucket位置上,它也是一個dictEntry指針,指向真正存儲key-value對的結構。因爲須要解決哈希衝突問題,因此一個bucket後面會以鏈地址法,經過next指針字段,掛接多個dictEntry,這就是上面代碼爲何須要遍歷的緣由。每遍歷一個dictEntry,咱們都要比較一下當前dictEntry上的key值是否與咱們要比較的key值是否相等,若是相等就找到了咱們要找的key-value對。反之若是遍歷到鏈表尾部都沒找到,那就說明沒有這個key-value對了:
咱們回顧一下dictEntry的結構:
typedef struct dictEntry { void *key; // 指向存儲key值的結構 union { void *val; // 指向存儲value值的結構 uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; // 鏈地址法解決衝突的指針,指向下一個dictEntry結構 } dictEntry;
在找完以後,該函數會返回一個dictEntry結構的指針,咱們調用dictGetVal宏,就能訪問到dictEntry中的value值啦:
#define dictGetVal(he) ((he)->v.val)
咱們看到,這個宏訪問了dictEntry的val字段,成功拿到了當前key對應的value值。