【Redis5源码学习】浅析redis命令之object篇

baiyan

命令使用

命令含义:查看指定key的一些信息,一般用于调试或查看内部编码使用
命令格式:

OBJECT subcommand [key]

OBJECT有4个可选的子命令subcommand:

  • OBJECT REFCOUNT:查看当前键的引用计数
  • OBJECT ENCODING:查看当前键的编码
  • OBJECT IDLETIME:查看当前键的空转时间
  • OBJECT FREQ:查看当前键最近访问频率的对数
  • OBJECT HELP:查看OBJECT命令的帮助信息

命令实战:

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> object refcount key1
(integer) 1
127.0.0.1:6379> object encoding key1
"embstr"
127.0.0.1:6379> object idletime key1
(integer) 20
127.0.0.1:6379> object idletime key1
(integer) 23
127.0.0.1:6379> object help
1) OBJECT  arg arg ... arg. Subcommands are:
2) ENCODING  -- Return the kind of internal representation used in order to store the value associated with a key.
3) FREQ  -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.
4) IDLETIME  -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.
5) REFCOUNT  -- Return the number of references of the value associated with the specified key.

返回值:REFCOUNT 和 IDLETIME 返回数字;ENCODING 返回相应的编码类型
注:idletime指该键空闲的时间,而空闲指没有被读取也没有被写入。set、get、ttl、expire命令都会重置idletime为0

源码分析

整个命令可以分为参数校验、查找字典两步。同样的,object命令的入口是objectCommand():

void objectCommand(client *c) {
    robj *o;

    // 如果执行OBJECT help命令,打印帮助信息
    if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
        const char *help[] = {
"ENCODING  -- Return the kind of internal representation used in order to store the value associated with a key.",
"FREQ  -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.",
"IDLETIME  -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
"REFCOUNT  -- Return the number of references of the value associated with the specified key.",
NULL
        };
        addReplyHelp(c, help); // 直接返回帮助信息
    } else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) { // 如果执行OBJECT refcount key命令
        if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) // 去键空间中查找该键的robj对象
                == NULL) return;
        addReplyLongLong(c,o->refcount); // 取出robj的refcount字段并返回
    } else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) { // 如果执行OBJECT encoding key命令
        if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk)) // 去键空间中查找该键的robj对象
                == NULL) return;
        addReplyBulkCString(c,strEncoding(o->encoding)); // 取出robj的encoding字段并返回
    } else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) { // 如果执行OBJECT idletime key命令
        if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))// 去键空间中查找该键的robj对象
                == NULL) return;
        if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { // 如果开启了LFU淘汰策略,就不会跟踪空转时间,无法使用命令
            addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
            return;
        }
        addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
    } else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) { //如果执行OBJECT freq key命令
        if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
                == NULL) return;
        if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
            addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
            return;
        }
        addReplyLongLong(c,LFUDecrAndReturn(o));
    } else { // 不是这几种子命令中的一个,直接报错
        addReplySubcommandSyntaxError(c);
    }
}

参数校验

由于OBJECT命令有多个子命令,所以需要进行参数校验,来判断是哪种子命令类型:

...
 else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) { // 如果执行OBJECT refcount key命令
...

字典查找

无论是哪种子命令类型,都是访问键的robj结构中的基础字段信息。要想获得这些信息,需要去字典中先去将该键结构查找出来:

        if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))== NULL) {
            return;
        }

这里通过调用objectCommandLookupOrReply()函数,实现了对键的查找:

robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
    robj *o = objectCommandLookup(c,key);

    if (!o) addReply(c, reply);
    return o;
}
robj *objectCommandLookup(client *c, robj *key) {
    dictEntry *de;
    // 去字典键空间中查找键
    if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
    return (robj*) dictGetVal(de); //根据键查找值,并强转为robj类型
}

找到键之后,直接通过引用robj中的某个字段(如refcount)就能够得到当前键引用计数的信息并返回,命令执行结束:

addReplyLongLong(c,o->refcount);

扩展

redis中的数据结构编码

先看下面一个例子:

redis> set foo 1000
OK
redis> object encoding foo
"int"
redis> append foo bar
(integer) 7
redis> get foo
"1000bar"
redis> object encoding foo
"raw"

以上例子表明,redis会根据输入的数据自动选择时间复杂度以及空间复杂度最低的数据结构来存储数据。如上例所示,在foo键的值为1000的时候,redis会选择int结构存储;而在其尾部追加bar之后,其值成为了“1000bar",就无法再使用int类型来存储了,故redis只能退一步使用raw结构来存储。
在切换数据结构的临界点的选择上,redis根据每种底层数据结构的增删改查的时间复杂度及空间复杂度,做了大量的权衡取舍。redis一共有五种基础数据结构,这五种数据结构的编码方式有如下几种选择:

  • 字符串可以被编码为int、embstr或raw。int为字符串可以转化为整数时采用,embstr在字符串较短时使用,raw可以表示任意长度的字符串
  • 列表可以被编码为 ziplist 或 linkedlist 。ziplist 是为节约大小较小的列表空间而作的特殊表示。
  • 集合可以被编码为 intset 或者 hashtable 。intset 是只储存数字的小集合的特殊表示。
  • 哈希表可以编码为 zipmap 或者 hashtable 。zipmap 是小哈希表的特殊表示。
  • 有序集合可以被编码为 ziplist 或者 skiplist 格式。ziplist 用于表示小的有序集合,而 skiplist 则用于表示任意大小的有序集合。

具体的每种数据结构的优缺点我们已经讨论过,不在此赘述。

你可能感兴趣的:(php,c,redis)