【Redis5源碼學習】淺析redis命令之object篇

baiyanredis

命令使用

命令含義:查看指定key的一些信息,通常用於調試或查看內部編碼使用
命令格式:數據結構

OBJECT subcommand [key]

OBJECT有4個可選的子命令subcommand:app

  • 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 <subcommand> arg arg ... arg. Subcommands are:
2) ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.
3) FREQ <key> -- 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 <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.
5) REFCOUNT <key> -- 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 <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
"FREQ <key> -- 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 <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
"REFCOUNT <key> -- 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結構中的基礎字段信息。要想得到這些信息,須要去字典中先去將該鍵結構查找出來:code

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

這裏經過調用objectCommandLookupOrReply()函數,實現了對鍵的查找:server

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 則用於表示任意大小的有序集合。

具體的每種數據結構的優缺點咱們已經討論過,不在此贅述。

相關文章
相關標籤/搜索