[redis 源碼走讀] 對象(redisObject)

redis 對象

redis 對數據的處理用對象進行管理,目前有5種類型。每種對象類型並不是用單一的編碼類型實現,根據應用場景,往往多種編碼類型結合使用。


對象

數據結構

typedef struct redisObject {
    // 對象類型
    unsigned type:4;
    // 對象編碼類型
    unsigned encoding:4;
    // 對象操作時間
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    // 使用計數
    int refcount;
    // 數據內容
    void *ptr;
} robj;

類型

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

編碼

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

創建對象

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution), or
     * alternatively the LFU counter. */
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    return o;
}

工作流程

對象實現可以通過調試,看看實現邏輯。剛看完整數集合,可以跟蹤下實現流程。

命令

sadd keytest 1 2 3

堆棧

下斷點看看命令工作流程

intsetNew() (/Users/xxx/src/other/redis/src/intset.c:100)
createIntsetObject() (/Users/xxx/src/other/redis/src/object.c:236)
setTypeCreate(sds value) (/Users/xxx/src/other/redis/src/t_set.c:44)
saddCommand(client * c) (/Users/xxx/src/other/redis/src/t_set.c:270)
call(client * c, int flags) (/Users/xxx/src/other/redis/src/server.c:3195)
processCommand(client * c) (/Users/xxx/src/other/redis/src/server.c:3552)
processCommandAndResetClient(client * c) (/Users/xxx/src/other/redis/src/networking.c:1651)
processInputBuffer(client * c) (/Users/xxx/src/other/redis/src/networking.c:1746)
processInputBufferAndReplicate(client * c) (/Users/xxx/src/other/redis/src/networking.c:1768)
readQueryFromClient(connection * conn) (/Users/xxx/src/other/redis/src/networking.c:1854)
callHandler(connection * conn, ConnectionCallbackFunc handler) (/Users/xxx/src/other/redis/src/connhelpers.h:76)
connSocketEventHandler(struct aeEventLoop * el, int fd, void * clientData, int mask) (/Users/xxx/src/other/redis/src/connection.c:275)
aeProcessEvents(aeEventLoop * eventLoop, int flags) (/Users/xxx/src/other/redis/src/ae.c:457)
aeMain(aeEventLoop * eventLoop) (/Users/xxx/src/other/redis/src/ae.c:515)
main(int argc, char ** argv) (/Users/xxx/src/other/redis/src/server.c:5054)

命令結構

struct redisCommand {
    char *name;
    redisCommandProc *proc;
    int arity;
    char *sflags;   /* Flags as string representation, one char per flag. */
    uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
    /* Use a function to determine keys arguments in a command line.
     * Used for Redis Cluster redirect. */
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? */
    int firstkey; /* The first argument that's a key (0 = no keys) */
    int lastkey;  /* The last argument that's a key */
    int keystep;  /* The step between first and last key */
    long long microseconds, calls;
    int id;     /* Command ID. This is a progressive ID starting from 0 that
                   is assigned at runtime, and is used in order to check
                   ACLs. A connection is able to execute a given command if
                   the user associated to the connection has this command
                   bit set in the bitmap of allowed commands. */
};
  • 命令綁定對應處理方法。
struct redisCommand redisCommandTable[] = {
    ...
    {"sadd",saddCommand,-3,
     "write use-memory fast @set",
     0,NULL,1,1,1,0,0,0},
    ...
}
  • 根據命令字符串,查找對應的 redisCommand
int processCommand(client *c) {
    /* Now lookup the command and check ASAP about trivial error conditions
     * such as wrong arity, bad command name and so forth. */
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
}
  • 命令處理函數
void saddCommand(client *c) {
    robj *set;
    int j, added = 0;

    // 查找 key 是否已經存在
    set = lookupKeyWrite(c->db,c->argv[1]);
    if (set == NULL) {
        // 根據命令數值去確定實現編碼。
        set = setTypeCreate(c->argv[2]->ptr);
        dbAdd(c->db,c->argv[1],set);
    } else {
        if (set->type != OBJ_SET) {
            addReply(c,shared.wrongtypeerr);
            return;
        }
    }

    for (j = 2; j < c->argc; j++) {
        if (setTypeAdd(set,c->argv[j]->ptr)) added++;
    }
    if (added) {
        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    }
    server.dirty += added;
    addReplyLongLong(c,added);
}
  • 根據存儲的數據,底層決定用那種編碼進行保存。
/* Factory method to return a set that *can* hold "value". When the object has
 * an integer-encodable value, an intset will be returned. Otherwise a regular
 * hash table. */
robj *setTypeCreate(sds value) {
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();
    return createSetObject();
}

robj *createIntsetObject(void) {
    intset *is = intsetNew();
    robj *o = createObject(OBJ_SET,is);
    o->encoding = OBJ_ENCODING_INTSET;
    return o;
}

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType,NULL);
    robj *o = createObject(OBJ_SET,d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}

參考

《redis 設計與實現》


後記

很多時候,我們理解面向對象,會單純認爲 C++java 等語言。所謂面向對象,是對事物邏輯進行抽象,無關語言,例如 c語言實現的 redis 對象,就很好闡述了這個問題。


[redis 源碼走讀] 系列,根據 《redis 設計與實現》書籍的目錄路線,結合相關內容進行源碼閱讀。計劃事無鉅細走讀源碼,發現時間不允許,很多細節在實際應用中可以不斷查閱鞏固,而且不少書籍博客也有很詳盡的解說。redis 在使用過程中,自己還有不少疑惑,帶着問題,在查閱過資料後,做一些總結,便於遺忘查閱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章