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

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 : 指向robj
int 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),该字条记录着对象的具体地址。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值