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. 总结
- Redis源码学习-NoSql复杂类型对象的hash管理(以set命令为例)
- Redis源码学习-NoSql复杂类型对象的hash管理(二:List)
- NoSQL之Redis---Hash类型命令
- NoSQL之Redis---Set类型命令
- NoSQL数据库Redis之游龙剑(hash类型)的修炼
- NoSQL数据库之Redis数据库管理二(string类型和hash类型)
- NoSQL数据库之Redis数据库管理二(string类型和hash类型)
- redis中 hash类型的操作命令
- redis中 hash类型的操作命令
- NoSQL数据库之Redis数据库管理四(set类型和zset类型)
- Redis学习--Hash类型
- PHP操作Redis LIST ,SET, HASH 的相关命令 (一)
- PHP操作Redis LIST ,SET, HASH 的相关命令 (一)
- PHP操作Redis LIST ,SET, HASH 的相关命令 (一)
- Redis--Hash类型命令操作
- Redis学习--Set类型
- 【捷哥浅谈PHP】第十九弹---NoSQL数据库Redis之游龙剑(hash类型)的修炼
- NoSQL之Redis---String类型命令
- C语言程序:尽可能多打印要求文本行
- 深入探索REST(2):理解本真的REST架构风格
- 在虚拟机中搭建wp8开发环境
- 字符串,字符和字节
- 61,DMS(03)
- Redis源码学习-NoSql复杂类型对象的hash管理(以set命令为例)
- ios 故事板
- 易信与微信语音技术对比
- 九度Online Judge解题报告
- ios 故事板2
- 使用VisualVM 查看远程Java进程
- 如何给你iOS项目选择最合适的XML解析方式
- Unable to instantiate Action, login, defined for 解决方法
- iOS使用多线程提高数据并发访问 之一