二:Redis的通用命令示例及其源码分析
通用命令指的是对所有数据类型都生效的命令。
keys *
-
解释: 显示查看Redis的所有键/匹配指定键
-
用法: keys * / keys pattern
keys *
-
示例
// 我们先存三个键 127.0.0.1:6379> set key1 value1 ok 127.0.0.1:6379> set key2 value2 ok 127.0.0.1:6379> set key3 value3 ok
127.0.0.1:6379> keys * 1) "key1" 2)"key2" 3) "key3"
-
源码分析
/* * redis.c 命令的结构体,定义redisCommand变量 */ struct redisCommand readonlyCommandTable[] = { // keys命令, keysCommand为keys命令的实现 {"keys",keysCommand,2,0,NULL,0,0,0} } /* *redis.h redisCommand结构体的声明 */ struct redisCommand { // 字符类型指针,客户端使用的命令名称 char *name; //命令处理函数 redisCommandProc *proc; int arity; int flags; /* Use a function to determine which keys need to be loaded * in the background prior to executing this command. Takes precedence * over vm_firstkey and others, ignored when NULL */ redisVmPreloadProc *vm_preload_proc; /* What keys should be loaded in background when calling this command? */ int vm_firstkey; /* The first argument that's a key (0 = no keys) */ int vm_lastkey; /* THe last argument that's a key */ int vm_keystep; /* The step between first and last key */ }; typedef void redisCommandProc(redisClient *c);
/* * db.c * keys 命令的处理函数 */ void keysCommand(redisClient *c) { //定义键值对的迭代器 dictIterator *di; //单个键值对 dictEntry *de; //keys命令的参数 keys * 则pattern= *,匹配所有的key,keys *1 则pattern=*1,匹配以1结尾的key sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; void *replylen = addDeferredMultiBulkLength(c); //当前数据库的所有键值对生成迭代器 di = dictGetIterator(c->db->dict); //是否查看所有的key,\0:判定字符数组结束的标志,表示这串字符到结尾了。 allkeys = (pattern[0] == '*' && pattern[1] == '\0'); //如果有下一个键值对,则执行when代码块里的逻辑 while((de = dictNext(di)) != NULL) { // typedef char *sds; 定义字符指针 //从键值对中获得键名称 sds key = dictGetEntryKey(de); robj *keyobj; // 如果查看所有的键或者符合匹配则 if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); //如果没有过期,添加到客户端返回结果集中 if (expireIfNeeded(c->db,keyobj) == 0) { addReplyBulk(c,keyobj); numkeys++; } decrRefCount(keyobj); } } //手动回收内存,dictGetEntryKey有开辟内存 dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,numkeys); }
dbsize
-
解释 : 统计当前数据库键的数量
-
用法 : dbsize
-
示例
//我们先存三个键 127.0.0.1:6379> set key1 value1 ok 127.0.0.1:6379> set key2 value2 ok 127.0.0.1:6379> set key3 value3 ok
127.0.0.1:6379> dbsize (integer) 3
-
源码分析
/** *redis.c */ struct redisCommand readonlyCommandTable[] = { {"dbsize",dbsizeCommand,1,0,NULL,0,0,0} } /** * db.c dbsize命令的处理函数 */ void dbsizeCommand(redisClient *c) { //c->db>dict当前数据库的键值对,dictSize计算键值对的大小,结果为长整型 //dict数据结构 hash tabale的实现 addReplyLongLong(c,dictSize(c->db->dict)); }
exists
-
解释:检查键是否存在,存在返回1,不存在返回0
-
用法: exists key
-
示例:
//存入一个键 127.0.0.1:6379> set key1 vakue1 127.0.0.1:6379> exists key1 (integer) 1 127.0.0.1:6379> exists key2 (integer) 0
-
源码分析:
/** *redis.c * redisCommand 结构体的声明 * exists 命令名,处理函数的声明 */ struct redisCommand readonlyCommandTable[] = { {"exists",existsCommand,2,0,NULL,1,1,1} } /** * db.c exists命令的处理函数 */ void existsCommand(redisClient *c) { // 如果key没有过期返回0,过期了删除Key expireIfNeeded(c->db,c->argv[1]); //是否在dict中找到查询的key if (dbExists(c->db,c->argv[1])) { //存在,返回1 addReply(c, shared.cone); } else { //不存在,返回0 addReply(c, shared.czero); } } /** * 在键值对数据结构dict中查找键key * */ int dbExists(redisDb *db, robj *key) { return dictFind(db->dict,key->ptr) != NULL; } /** * redis.c * 启动redis服务的时候,会执行这个方法,初始化czero,cone * */ void createSharedObjects(void) { shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n")); shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n")); }
del
- 解释:删除已存在的键,返回1,不存在的键会被忽略,返回0
- 用法:del key
- 示例:
127.0.0.1:6379> set key1 value1
ok
127.0.0.1:6379> del key1
(integer) 1
127.0.0.1:6379> del key2
(integer) 0
- 源码分析:
/**
* redis.c
* edisCommand 结构体的声明
* 声明del命令的处理函数
*/
struct redisCommand readonlyCommandTable[] = {
{"del",delCommand,-2,0,NULL,0,0,0}
}
/*
*db.c
* del命令的处理函数
*/
void delCommand(redisClient *c) {
int deleted = 0, j;
//删除多个键,每次删除一个键
for (j = 1; j < c->argc; j++) {
// 执行删除操作
if (dbDelete(c->db,c->argv[j])) {
//检查数据库的watched_keys字典,看是否有客户端在监视已经被
//命令修改的键,如果有的话,程序将所有监视这个被修改的键的
//客户端的REDIS_DIRTY_CAS打开:
//当客户端发送EXEC命令,触发事物执行时,服务端会对客户端的状态检查:
//1.如果客户端的REDIS_DIRTY_CAS选项已经被打开了,那么说明被客户端监视的键至少有一个已经被修改了,
//事物的安全性已经被破坏,服务器会放弃执行这个事务,直接向客户端会空回复,表示事务执行失败
//2.如果REDIS_DIRTY_CAS选项没有被打开,那么说明所有监视键都安全,服务器正式执行事务。
touchWatchedKey(c->db,c->argv[j]);
server.dirty++;
deleted++;
}
}
//返回删除成功键的个数
addReplyLongLong(c,deleted);
}
expire
- 解释:设置键的过期时间,单位秒,键过期后不再可用。
- 用法: expire key
- 示例:
127.0.0.1:6379> set key1 value1 ok 127.0.0.1:6379> get key1 "value1" 127.0.0.1:6379> expire key1 1 (integer) 1 //一秒后再查看键,键过期 127.0.0.1:6379> get key1 (nil)
- 源码分析:
/**
* redis.c
* edisCommand 结构体的声明
* 声明expire命令的处理函数
*/
struct redisCommand readonlyCommandTable[] = {
{"expire",expireCommand,3,0,NULL,0,0,0}
}
/**
* db.c
* expire命令的处理函数
**/
void expireCommand(redisClient *c) {
// argv[1]:设置过期的键, argc[2]:设置键的过期时间
expireGenericCommand(c,c->argv[1],c->argv[2],0);
}
/**
* db.c
**/
void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
dictEntry *de;
long seconds;
// 从param中取出要设置的长整型多少秒后过期,如果失败,则函数直接返回
if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return;
seconds -= offset;
// 在键值对dict中查找键,
de = dictFind(c->db->dict,key->ptr);
//如果键不存在,则给客户端返回0
if (de == NULL) {
addReply(c,shared.czero);
return;
}
// 在载入AOF数据时,或者服务器为附属节点时,
// 即使EXPIRE的TTL为负数,expireat提供的时间戳已经过期
// 服务器也不会主动删除这个键,而是等待主节点发来显示的DEL命令。
if (seconds <= 0 && !server.loading && !server.masterhost) {
robj *aux;
redisAssert(dbDelete(c->db,key));
server.dirty++;
/* Replicate/AOF this as an explicit DEL. */
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,key);
decrRefCount(aux);
touchWatchedKey(c->db,key);
addReply(c, shared.cone);
return;
} else {
//键的过期时间
time_t when = time(NULL)+seconds;
//设置键的过期时间
setExpire(c->db,key,when);
//返回给客户端成功
addReply(c,shared.cone);
touchWatchedKey(c->db,key);
server.dirty++;
return;
}
}
/**
* db.c
* 键值对设置键的过期时间
**/
void setExpire(redisDb *db, robj *key, time_t when) {
// 从dict中返回键
dictEntry *de;
de = dictFind(db->dict,key->ptr);
redisAssert(de != NULL);
//如果之前有过期值更新值,如果没有添加
dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
}
type
- 解释: 显示键的数据结构类型
- 用法: type key
- 示例:
127.0.0.1:6379> set key1 value1 ok 127.0.0.1:6379> get key1 "value1" 127.0.0.1:6379> type key1 string
- 源码分析:
/**
* redis.c
* edisCommand 结构体的声明
* 声明type命令的处理函数
*/
struct redisCommand readonlyCommandTable[] = {
{"type",typeCommand,2,0,NULL,1,1,1},
}
/**
* db.c
* type命令处理函数的实现
*/
void typeCommand(redisClient *c) {
robj *o;
char *type;
//查询key=argv[1],返回robi结构
o = lookupKeyRead(c->db,c->argv[1]);
if (o == NULL) {
type = "none";
} else {
// 键的类型定义,是否在五种类型中
switch(o->type) {
case REDIS_STRING: type = "string"; break;
case REDIS_LIST: type = "list"; break;
case REDIS_SET: type = "set"; break;
case REDIS_ZSET: type = "zset"; break;
case REDIS_HASH: type = "hash"; break;
default: type = "unknown"; break;
}
}
addReplyStatus(c,type);
}
redis有五种数据类型:string(字符),set(集合),list(列表),sortset(有序集合),hash(哈希)后面我们会详细介绍这几个数据类型。
object encoding
- 解释:显示键的内部编码。
- 用法: object encoding key
- 示例:
127.0.0.1:6379> set key1 value1 ok 127.0.0.1:6379> get key1 "value1" 127.0.0.1:6379> object encoding key "embstr"
- 源码分析:
/**
* redis.c
* edisCommand 结构体的声明
* 声明object命令的处理函数
*/
struct redisCommand readonlyCommandTable[] = {
{"object",objectCommand,-2,0,NULL,0,0,0}
}
/**
*
* object.c
**/
void objectCommand(redisClient *c) {
robj *o;
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,o->refcount);
//显示键的内部编码
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding));
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,estimateObjectIdleTime(o));
} else {
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
}
}
/**
*robj的定义
**/
robj *o;
//c从键值对dict中查找出 robj
o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)
//声明robj结构
typedef struct redisObject {
unsigned type:4;
unsigned storage:2; /* REDIS_VM_MEMORY or REDIS_VM_SWAPPING */
unsigned encoding:4;
unsigned lru:22; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
Redis对外提供的五种数据类型在其内部每一个都有多个不同的数据结构实现。使用内部编码的好处是,
1.用户使用简单,字符类型在内部有两种实现。
2.当Redis实现了一种更高效的数据结构实现时,对用户是无影响的。
后面我们会详细分析这里。
本文基于Redis2.2源码进行分析。