redis的key操作命令包括exists,del,type,keys,randomkey,rename和renamenx命令.这一组命令均是操作key,因此命令参数是key.

redis exists命令
redis exists命令的格式为exists key, 判断包含指定key的哈希节点是否存在,如果存在,返回1,否则,返回0. 需要注意的是,如果包含该key的哈希节点存在,但是其value也可能是空的.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey 7
myvalue
+OK
exists mykey
1

redis exists命令对应的处理函数为existsCommand, 其实现为(redis.c):

1372 static void existsCommand(redisClient *c) {
   1373     dictEntry *de;
   1374 
   1375     de = dictFind(c->dict,c->argv[1]);
   1376     if (de == NULL)
   1377         addReply(c,shared.zero);
   1378     else
   1379         addReply(c,shared.one);
   1380 }

redis type命令
redis type命令的格式为type key, 该命令返回key对应的value类型, value类型包括string, list和set.如果包含key的哈希节点不存在,则返回none. telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey 7
myvalue
+OK
type mykey
string

redis type命令对应的处理函数为typeCommand,其实现为(redis.c):

1485 static void typeCommand(redisClient *c) {
1486     dictEntry *de;
1487     char *type;
1488 
1489     de = dictFind(c->dict,c->argv[1]);
1490     if (de == NULL) {
1491         type = "none";
1492     } else {
1493         robj *o = dictGetEntryVal(de);
1494 
1495         switch(o->type) {
1496         case REDIS_STRING: type = "string"; break;
1497         case REDIS_LIST: type = "list"; break;
1498         case REDIS_SET: type = "set"; break;
1499         default: type = "unknown"; break;
1500         }
1501     }
1502     addReplySds(c,sdsnew(type));
1503     addReply(c,shared.crlf);
1504 }

Line1489在数据库中查找包含指定key的哈希节点.如果没有找到该节点,返回none字符串,否则,根据value的类型,返回相应的字符串.

redis randomkey命令
redis randomkey命令的格式为randomkey, 该命令从数据库中返回一个随机key.注意是从该客户端已选择的数据库中.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey 7
myvalue
+OK
type mykey
string
randomkey
mykey

redis randomkey命令对应的处理函数为randomkeyCommand,其实现为(redis.c):

1437 static void randomkeyCommand(redisClient *c) {
1438     dictEntry *de;
1439 
1440     de = dictGetRandomKey(c->dict);
1441     if (de == NULL) {
1442         addReply(c,shared.crlf);
1443     } else {
1444         addReplySds(c,sdsdup(dictGetEntryKey(de)));
1445         addReply(c,shared.crlf);
1446     }
1447 }

Line1440调用函数dictGetRandomKey从该客户端已选择的数据库中返回一个随机哈希节点.该函数的实现为(dict.c):

342 /* Return a random entry from the hash table. Useful to
343  * implement randomized algorithms */
344 dictEntry *dictGetRandomKey(dict *ht)
345 {
346     dictEntry *he;
347     unsigned int h;
348     int listlen, listele;
349 
350     if (ht->size == 0) return NULL;
351     do {
352         h = random() & ht->sizemask;
353         he = ht->table[h];
354     } while(he == NULL);
355 
356     /* Now we found a non empty bucket, but it is a linked
357      * list and we need to get a random element from the list.
358      * The only sane way to do so is to count the element and
359      * select a random index. */
360     listlen = 0;
361     while(he) {
362         he = he->next;
363         listlen++;
364     }
365     listele = random() % listlen;
366     he = ht->table[h];
367     while(listele--) he = he->next;
368     return he;
369 }

Line350如果哈希表为空,返回NULL.Line351:354随机选择哈希表中的一个BUCKET索引,要求其链表非空.Line360:364计算该BUCKET对应的链表中的节点个数.Line365:368在该链表中随机选取一个节点并返回.

回到函数randomkeyCommand,Line1441:1446如果返回的随机哈希节点为空,则向客户端发送空提示字符串,否则,发送该哈希节点包含的key.

redis rename命令
redis rename命令的格式为rename oldkey newkey,该命令的含义是将包含oldkey的哈希节点,以newkey重新记录于数据库中,因为key发生了改变,需要基于newkey做映射,并释放oldkey的内存.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey1 7
myvalue
+OK
rename mykey1 mykey2
+OK

redis rename命令对应的处理函数为renameCommand,其实现为(redis.c):

1569 static void renameCommand(redisClient *c) {
1570     renameGenericCommand(c,0);
1571 }

函数renameGenericCommand的实现为(redis.c):

1537 static void renameGenericCommand(redisClient *c, int nx) {
1538     dictEntry *de;
1539     robj *o;
1540 
1541     /* To use the same key as src and dst is probably an error */
1542     if (sdscmp(c->argv[1],c->argv[2]) == 0) {
1543         addReplySds(c,sdsnew("-ERR src and dest key are the same\r\n"));
1544         return;
1545     }
1546 
1547     de = dictFind(c->dict,c->argv[1]);
1548     if (de == NULL) {
1549         addReplySds(c,sdsnew("-ERR no such key\r\n"));
1550         return;
1551     }
1552     o = dictGetEntryVal(de);
1553     incrRefCount(o);
1554     if (dictAdd(c->dict,c->argv[2],o) == DICT_ERR) {
1555         if (nx) {
1556             decrRefCount(o);
1557             addReplySds(c,sdsnew("-ERR destination key exists\r\n"));
1558             return;
1559         }
1560         dictReplace(c->dict,c->argv[2],o);
1561     } else {
1562         c->argv[2] = NULL;
1563     }
1564     dictDelete(c->dict,c->argv[1]);
1565     server.dirty++;
1566     addReply(c,shared.ok);
1567 }

Line1541:1545如果oldkey和newkey相同,则向客户端发送错误提示字符串.Line1547:1551获得oldkey对应的哈希节点,如果该节点不存在,则向客户端返回错误提示字符串.Line1552:1553获得哈希节点中的robj对象,并增加其应用计数.Line1554:1563将newkey/value对应的哈希节点存入数据库.Line1560如果newkey已经存在,进行value替换.Line1564调用函数dictDelete删除oldkey对应的哈希节点,该函数的实现为(dict.c):

248 int dictDelete(dict *ht, const void *key) {
249     return dictGenericDelete(ht,key,0);
250 }

由于传入函数dictGenericDelete的第三个参数为0, 所以该函数会释放哈希节点的key和value,value的释放函数是基于引用计数,如果robj对象的引用计数为0,才真正释放robj对象封装的value.因此可以看到函数renameGenericCommand中Line15533中增加robj对象引用计数的含义,因为rename操作只需要释放oldkey而不需要释放value.

回到函数renameGenericCommand, Line1565:1566更新数据库更新计数,并向客户端返回操作成功提示字符串.

redis renamenx命令
redis renamenx命令的格式为renamenx oldkey newkey,该命令的含义是如果包含newkey的哈希节点不存在,将包含oldkey的哈希节点,以newkey重新记录于数据库中.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey1 7
myvalue
+OK
renamenx mykey1 mykey2
+OK

redis renamenx命令对应的处理函数为renamenxCommand,其实现为(redis.c):

1573 static void renamenxCommand(redisClient *c) {
   1574     renameGenericCommand(c,1);
   1575 }

和rename命令的区别在于函数renameGenericCommand Line1555:1559,如果newkey对应的哈希节点已经存在,则向客户端返回错误提示字符串.

redis keys命令
redis keys命令的格式为keys pattern,该命令从数据库中返回匹配glob格式的key.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey1 8
myvalue1
+OK
set mykey2 8
myvalue2
+OK
keys mykey*
13
mykey1 mykey2

redis keys命令对应的处理函数为keysCommand,其实现为(redis.c):

1449 static void keysCommand(redisClient *c) {
1450     dictIterator *di;
1451     dictEntry *de;
1452     sds keys, reply;
1453     sds pattern = c->argv[1];
1454     int plen = sdslen(pattern);
1455 
1456     di = dictGetIterator(c->dict);
1457     keys = sdsempty();
1458     while((de = dictNext(di)) != NULL) {
1459         sds key = dictGetEntryKey(de);
1460         if ((pattern[0] == '*' && pattern[1] == '\0') ||
1461             stringmatchlen(pattern,plen,key,sdslen(key),0)) {
1462             keys = sdscatlen(keys,key,sdslen(key));
1463             keys = sdscatlen(keys," ",1);
1464         }
1465     }
1466     dictReleaseIterator(di);
1467     keys = sdstrim(keys," ");
1468     reply = sdscatprintf(sdsempty(),"%lu\r\n",sdslen(keys));
1469     reply = sdscatlen(reply,keys,sdslen(keys));
1470     reply = sdscatlen(reply,"\r\n",2);
1471     sdsfree(keys);
1472     addReplySds(c,reply);
1473 }

Line1453:1454获得模式字符串机器长度.Line1456动态创建一个哈希表遍历器.Line1457创建一个sds对象,用来容纳匹配的key.Line1458:1465依次遍历哈希表中的节点,如果其key和模式字符串匹配,记录该key.Line1466释放动态创建的哈希表遍历器.Line1467:1472构造响应内容,并发送给客户端.