1.数据结构
NoSql的核心是实现基于内存的K-V的快速查找。在Redis中,Hash结构就是实现k-v快速查找的核心结构,名为dict。此外,Redis的犀利之处是在于能够将整个复杂的数据类型(set,list...)打包存储。这个hash结构(dict :http://blog.youkuaiyun.com/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.youkuaiyun.com/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++;
}