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值。