深入底层C源码 Redis核心设计原理( 三 )

代码说明
【1】sds内部buf的扩容机制:新buf长度 = (原buf长度 + 添加buf长度)*2 , 如果buf长度大于1M后,每次扩容也只会增大1M
【2】对于类型改变的需要变换存储空间 。
3.RedisDb 数据结构1)代码展示
//位于server.h文件中typedef struct redisDb {dict *dict;// 保存了当前数据库的键空间dict *expires;//键空间中所有键的过期时间dict *blocking_keys;//客户端等待数据的键(BLPOP)dict *ready_keys;//保存着处于阻塞状态的键 , value为NULLdict *watched_keys;//监视键的MULTI/EXEC CASint id;//数据库IDlong long avg_ttl;//键的平均过期时间unsigned long expires_cursor; //周期性删除过期键的游标list *defrag_later;/* List of key names to attempt to defrag one by one, gradually. */} redisDb;//位于dict.h文件中typedef struct dict {dictType *type;void *privdata;dictht ht[2]; // ht[0] , ht[1] =null //方便渐进的rehash扩容,dict的hashtable,其中?一个哈希表正常存储数据?,?另一个哈希表为空,空哈希表在 rehash 时使用long rehashidx; /* rehash 索引,当不在进行 rehash 时 , 值为 -1 */unsigned long iterators; //当前正在运行的迭代器的数量} dict;//位于dict.h文件中/*这是我们的哈希表结构 。每本字典都有两个这样的词,实现增量重哈希 , 从旧表到新表 。* /typedef struct dictht {dictEntry **table;unsigned long size; //hashtable 容量unsigned long sizemask;// size -1unsigned long used;// hashtable 元素个数used / size =1} dictht;//位于dict.h文件中typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;} dictEntry;//位于server.h文件中//redisObject对象 :string , list ,set ,hash ,zset ...typedef struct redisObject {unsigned type:4;//4 bit, sting , hashunsigned encoding:4;//4 bitunsigned 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).*24 bit* */int refcount;// 4 bytevoid *ptr;// 8 byte总空间:4 bit + 4 bit + 24 bit + 4 byte + 8 byte = 16 byte} robj;2)视图展示
 

深入底层C源码 Redis核心设计原理

文章插图
3)代码说明
【1】由上可知redisDb,主要都是将数据存储在字典(dict)中 , 而且还是多个,固定存储 , 过期维护等多个字典 。
【2】dict字典结构,每个字典有两个哈希表结构的原因是为了用于渐进式扩容,当某个哈希表结构过于庞大的时候(按照hashMap的思维,必定是需要对数组进行扩容,增大数组长度 , 将链表长度缩?。涌毂槔?其实它也需要进行扩容 , 但是再进行扩容操作的同时,容易出现阻塞线程的情况(如果时间太久) , 为此,dict中采用rehashidx标明是否正在处于扩容状态,且ht[1]会生成一个新的哈希表结构,容量是之前的两倍,然后把ht[0]中的数据按槽位一点一点的搬运过来【断断续续的操作,这样就不会一直阻塞住线程】,新的数据也会落到ht[1]中,直到搬完 。然后将ht[1]指针指向ht[0],然后自己再指向null,rehashidx变为0,就完成了扩容操作 。
【3】dictEntry相当于hashMap中的节点(包含了key,value,和指向下个节点的指针),其中val会被进一步封装成redisObject 。
【4】redisObject中的type用于约束客户端命令 , 如set操作,会判断操作的值与操作的类型匹不匹配 。encoding记录了值在redis底层是怎么样的编码形式 。ptr指向内存的真实地址 。
4)分析String类型的编码
【1】会存在:int,raw,embstr三种 。
【2】为什么会有int,因为整型值最大固定是64bit , 其实与指针*ptr占据的大小一致,其实把数值存于这里可以减少了对空间的开辟 。代码展示:
//server.c文件中封装了所有的客户端命令//发现set命令会执行setCommand方法【该方法位于t_string.c文件中】 , 直接看核心部分void setCommand(client *c) {....// 完成编码set:keyvaluec->argv[2] = tryObjectEncoding(c->argv[2]);setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);}//该方法位于object.c文件中robj *tryObjectEncoding(robj *o) {long value;sds s = o->ptr;size_t len;/*确保这是一个字符串对象,我们在这个函数中编码的唯一类型 。其他类型使用编码的内存高效表示,但由实现该类型的命令处理 。* /serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);//只有类型为 原生sds类型 或者embstr类型,还有机会可以进一步编码,否则直接返回if (!sdsEncodedObject(o)) return o;// 如果其他地方有应用即当前对象为共享对象,修改范围将扩大,所以放弃编码为整形操作if (o->refcount > 1) return o;//判断是否可以把该字符串转化为一个长整型len = sdslen(s);//范围是否在 整型值得表示范围,0 - 2^64,最多不超过20 位if (len <= 20 && string2l(s,len,&value)) {/**如果Redis的配置不要求运行LRU替换算法,且转成的long型数字的值又比较小*(小于OBJ_SHARED_INTEGERS,在目前的实现中这个值是10000),*那么会使用共享数字对象来表示 。之所以这里的判断跟LRU有关,是因为LRU算法要求每个robj有不同的lru字段值,*所以用了LRU就不能共享robj 。shared.integers是一个长度为10000的数组,里面预存了10000个小的数字对象 。*这些小数字对象都是 encoding = OBJ_ENCODING_INT的string robj对象 。** */// 没有设置内存淘汰策略,且数字范围在 缓存整型得范围内if ((server.maxmemory == 0 ||!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&value >= 0 &&value < OBJ_SHARED_INTEGERS){decrRefCount(o); // 不需要用额外得对象来存储incrRefCount(shared.integers[value]);return shared.integers[value];// 共享对象} else {// 如果前一步不能使用共享小对象来表示 , 那么将原来的robj编码成encoding = OBJ_ENCODING_INT,这时ptr字段直接存成这个long型的值 。// 注意ptr字段本来是一个void *指针(即存储的是内存地址),// 因此在64位机器上有64位宽度 , 正好能存储一个64位的long型值 。这样,除了robj本身之外 , 它就不再需要额外的内存空间来存储字符串值 。if (o->encoding == OBJ_ENCODING_RAW) {sdsfree(o->ptr); // 释放空间o->encoding = OBJ_ENCODING_INT;// 用整形编码o->ptr = (void*) value;return o;} else if (o->encoding == OBJ_ENCODING_EMBSTR) {decrRefCount(o);return createStringObjectFromLongLongForValue(value);}}}// 数据长度 小于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44的话,用 embstr 进行编码if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {robj *emb;if (o->encoding == OBJ_ENCODING_EMBSTR) return o;emb = createEmbeddedStringObject(s,sdslen(s));decrRefCount(o);return emb;}trimStringObjectIfNeeded(o);/* Return the original object. */return o;}

推荐阅读