美文网首页
[redis 源码走读] 对象(redisObject)

[redis 源码走读] 对象(redisObject)

作者: wenfh2020 | 来源:发表于2020-02-05 04:39 被阅读0次

    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 redisServer {
        ...
        dict *commands;             /* Command table */
        dict *orig_commands;        /* Command table before command renaming. */
        ...
    };
    
    struct redisCommand redisCommandTable[] = {
        ...
        {"sadd",saddCommand,-3,
         "write use-memory fast @set",
         0,NULL,1,1,1,0,0,0},
        ...
    }
    
    int main(int argc, char **argv) {
        ...
        initServerConfig();
        ...
    }
    
    void initServerConfig(void) {
        ...
        /* Command table -- we initiialize it here as it is part of the
         * initial configuration, since command names may be changed via
         * redis.conf using the rename-command directive. */
        server.commands = dictCreate(&commandTableDictType,NULL);
        server.orig_commands = dictCreate(&commandTableDictType,NULL);
        populateCommandTable();
        server.delCommand = lookupCommandByCString("del");
        ...
    }
    
    /* Populates the Redis Command Table starting from the hard coded list
     * we have on top of redis.c file. */
    void populateCommandTable(void) {
        int j;
        int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);
    
        for (j = 0; j < numcommands; j++) {
            struct redisCommand *c = redisCommandTable+j;
            int retval1, retval2;
    
            /* Translate the command string flags description into an actual
             * set of flags. */
            if (populateCommandTableParseFlags(c,c->sflags) == C_ERR)
                serverPanic("Unsupported command flag");
    
            c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
            retval1 = dictAdd(server.commands, sdsnew(c->name), c);
            /* Populate an additional dictionary that will be unaffected
             * by rename-command statements in redis.conf. */
            retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
            serverAssert(retval1 == DICT_OK && retval2 == DICT_OK);
        }
    }
    
    • 根据命令字符串,查找对应的 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;
    }
    

    其它

    以上是集合的工作流程,其它功能使用流程以此类推,当然你也可以走下哈希功能。编码是 dictziplist 的结合。推例如:

    hset keytest filedtest valuetest
    
    robj *createHashObject(void) {
        unsigned char *zl = ziplistNew();
        robj *o = createObject(OBJ_HASH, zl);
        o->encoding = OBJ_ENCODING_ZIPLIST;
        return o;
    }
    
    void hsetCommand(client *c) {
        ...
        if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
        hashTypeTryConversion(o,c->argv,2,c->argc-1);
    
        for (i = 2; i < c->argc; i += 2)
            created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
        ...
    }
    
    void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
        ...
        for (i = start; i <= end; i++) {
            if (sdsEncodedObject(argv[i]) &&
                sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
            {
                hashTypeConvert(o, OBJ_ENCODING_HT);
                break;
            }
        }
    }
    
    int hashTypeSet(robj *o, sds field, sds value, int flags) {
        int update = 0;
    
        if (o->encoding == OBJ_ENCODING_ZIPLIST) {
            ...
        } else if (o->encoding == OBJ_ENCODING_HT) {
            ...
        } else {
            serverPanic("Unknown hash encoding");
        }
    }
    

    参考

    《redis 设计与实现》


    后记

    很多时候,我们理解面向对象,会单纯认为 C++java 等语言。所谓面向对象,是对事物逻辑进行抽象,无关语言,例如 c语言实现的 redis 对象,就很好阐述了这个问题。


    [redis 源码走读] 系列,根据 《redis 设计与实现》书籍的目录路线,结合相关内容进行源码阅读。计划事无巨细走读源码,发现时间不允许,很多细节在实际应用中可以不断查阅巩固,而且不少书籍博客也有很详尽的解说。redis 在使用过程中,自己还有不少疑惑,带着问题,在查阅过资料后,做一些总结,便于遗忘查阅。


    更精彩内容,请关注我的博客:https://wenfh2020.com

    相关文章

      网友评论

          本文标题:[redis 源码走读] 对象(redisObject)

          本文链接:https://www.haomeiwen.com/subject/qxsjxhtx.html