Redis源码学习-NoSql复杂类型对象的hash管理(以set命令为例)

来源:互联网 发布:dede tag标签源码 编辑:程序博客网 时间:2024/05/19 06:50

1.数据结构        

        NoSql的核心是实现基于内存的K-V的快速查找。在Redis中,Hash结构就是实现k-v快速查找的核心结构,名为dict。此外,Redis的犀利之处是在于能够将整个复杂的数据类型(set,list...)打包存储。这个hash结构(dict :http://blog.csdn.net/ordeder/article/details/12836017 )的设计如下:

typedef struct dict {    dictType *type;/*不同数据类型对应的相关的操作hander*/    void *privdata;    dictht ht[2];/*ht[0]作为dict的实际hash结构,ht[1]做为扩容阶段的转储结构*/    int rehashidx; /*标志dict是否处于rehash阶段,如果值为-1,表示不处于rehash。否则,在rehash中所谓hashtalbe的索引下标*/    int iterators; /* number of iterators currently running */} dict;typedef struct dictType {    unsigned int (*hashFunction)(const void *key);    void *(*keyDup)(void *privdata, const void *key);    void *(*valDup)(void *privdata, const void *obj);    int (*keyCompare)(void *privdata, const void *key1, const void *key2);    void (*keyDestructor)(void *privdata, void *key);    void (*valDestructor)(void *privdata, void *obj);} dictType;typedef struct dictht {    dictEntry **table;//hash表,每个table[i]链表存储着一个hashkey相等的dictEntry指针    unsigned long size;//table[]的大小    unsigned long sizemask;// = size-1    unsigned long used;//当前table中存储的dictEntry指针的个数} dictht;typedef struct dictEntry {    void *key;    void *val;//这里作者用空类型指针存储指向不同数据类型对象redisObject(set,list...)的指针    struct dictEntry *next;} dictEntry;
        不同对象的“包装”采用了redisObject结构。每个类型的数据(set,list...)都被抽象为一个object,从而不同类型的数据可以统一进行hash,因为,在dict中为每个数据类型的obj记录的是对象指针而已。(见 dictEntry.val)。每种复杂对象(set,list...)都被打包成为robj对象,而结构中redisObject->ptr才是这个复杂对象在内存中的真正存储地址。
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的val值} robj;

    通过以上的数据结构,我们可以这样理解:dict为建立了一个k-v结构的维护框架,这个框架上挂着用redisObject结构描述的数据对象(包装),这个“包装”中描述了这个对象的类型,存储编码,具体在内存中的地址等等信息。这样看来,这个redisObject倒有点像Linux内核中的page结构。

    上文说的复杂类型数据其实是相对于C语言而言,Redis能够支持整个双向链表(list)等的hash检索,而我们常用的C对数据的hash是基于基本类型:int,char,double等基本类型的hash。Redis对字符串、链表、hash这些复杂类型的定义如下:

//字符串类型struct sdshdr {    int len;    int free;    char buf[];};//list类型(双向链表)typedef struct list {    listNode *head;    listNode *tail;    void *(*dup)(void *ptr);    void (*free)(void *ptr);    int (*match)(void *ptr, void *key);    unsigned int len;} list;//hash类型typedef struct dict {...}dict
    一个哈希表复杂类型的存储同样是dict结构,很巧妙有木有。虽然不同的数据类型实现方式和占用内存截然不同,Redis用redisObject进行统一"包裹",这样对于dict而言,屏蔽了类型的差异性。

2.命令分析:set

set(key, value):给数据库中名称为key的string赋予值value.
比如执行:set name ordeder
1.server得到客户端的命令到querybuf中,然后通过processInputBuffer()函数进行解析,将各个参数以robj的形式保存,接着进入命令执行函数call(c,cmd)(参考 http://blog.csdn.net/ordeder/article/details/16105345)

2. call对cmd进行解析,从而进入setCommand(),它是作为set命令的入口函数。

//key c->argv[1] :name(boj); val c->argv[2] : ordeder (boj)void setCommand(redisClient *c) {    c->argv[2] = tryObjectEncoding(c->argv[2]);    setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);}void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) {    int retval;    long seconds = 0; /* initialized to avoid an harmness warning */    if (expire) {        if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)            return;        if (seconds <= 0) {            addReplyError(c,"invalid expire time in SETEX");            return;        }    }    retval = dbAdd(c->db,key,val);//NEXT    if (retval == REDIS_ERR) {        if (!nx) {            dbReplace(c->db,key,val);            incrRefCount(val);//对象的引用+1,引用者为db->dict->ht[i]->dictEntry...        } else {            addReply(c,shared.czero);            return;        }    } else {        incrRefCount(val);//对象的引用+1,引用者为db->dict->ht[i]->dictEntry...    }    touchWatchedKey(c->db,key);    server.dirty++;    removeExpire(c->db,key);    if (expire) setExpire(c->db,key,time(NULL)+seconds);    addReply(c, nx ? shared.cone : shared.ok);}int dbAdd(redisDb *db, robj *key, robj *val) {    /* Perform a lookup before adding the key, as we need to copy the     * key value. */    if (dictFind(db->dict, key->ptr) != NULL) {        return REDIS_ERR;    } else {//sds 的定义 typedef char *sds        sds copy = sdsdup(key->ptr);//从rojb key中拷贝key的值(字符串格式)        dictAdd(db->dict, copy, val);//NEXT,val还是个对象指针,copy是字符串指针        return REDIS_OK;    }}//*key : 指向string; *val : 指向robjint dictAdd(dict *d, void *key, void *val){    int index;    dictEntry *entry;    dictht *ht;//处于rehash状态但是还未真正开始rehash//那么_dictRehashStep 启动一次rehash...    if (dictIsRehashing(d)) _dictRehashStep(d);    /* Get the index of the new element, or -1 if     * the element already exists. */    if ((index = _dictKeyIndex(d, key)) == -1)        return DICT_ERR;    /* Allocates the memory and stores key *///rehash: 将ht[0] 中的element搬到ht[1]中,So~    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];    entry = zmalloc(sizeof(*entry));    entry->next = ht->table[index];    ht->table[index] = entry;    ht->used++;    /* Set the hash entry fields. */    dictSetHashKey(d, entry, key);    dictSetHashVal(d, entry, val); //entry->val指针被赋值    return DICT_OK;}//存储robj *val, 注意,这里保存的是robj指针!而非val对象本身#define dictSetHashVal(d, entry, _val_) do { \    if ((d)->type->valDup) \        entry->val = (d)->type->valDup((d)->privdata, _val_); \    else \        entry->val = (_val_); \} while(0)void incrRefCount(robj *o) {    o->refcount++;}

3. 总结

      将dict比作架子,架子上的钩钩(dictEntry->val)挂着的包裹(redisObject),而包裹中有一个字条(redisObject->prt),该字条记录着对象的具体地址。
原文链接:http://blog.csdn.net/ordeder/article/details/16893621