您的位置:首页 > 理论基础 > 数据结构算法

REDIS 字典数据结构

2014-08-30 16:36 260 查看
对于REDIS来讲其实就是一个字典结构,key---->value就是一个典型的字典结构

【当然对于vaule来讲的话,有不同的内存组织结构这是后话】

试想一个这样的存储场景:
key:"city"

value:"beijing"

如果有若干个这样的键值对,你该怎么去存储它们呢要保证写入和查询速度非常理想~!

抛开redis不说,如果你想要存储快速查找的话,Hash算法是最快的,理想的哈希函数可以带来O(1)的查找速度,你都这样想,那么redis也的确采用这种方法来做~!

但是HASH算法有2个致命的弱点:1)填充因子不能太满2)不好的HASH算法可能会导致一个冲突率非常高。

填充因子不能太满
这个理论上一般为0.5左右过高就是哈希槽都被塞满了,即使在好的哈希分布算法也无法避免key冲突。
不好的哈希分布算法
丢到第一个因素来讲,如果一个不好的哈希分布算法会导致了key分布不均匀,也就是通过哈希函数计算出来的哈希槽都是落在了一个桶里,这样的哈希分布算法是最不理想的,最理想的情况下是保证每个key都落在不同的哈希槽里【哈希槽>key】

实际存储的哈希存储设计
1)一般来讲,哈希分布函数确定后,可调控的因子就是这个填充因子如果填充因子大于你卡的某个阈值,那么你就要做哈希结构迁移工作,迁移到一个更大的哈希槽中。而对用同用的这种哈希分布函数,有许多人用各种数学方法计算过,这里也没有深入研究这个分布函数,倒是在这个填充因子上面,卡的阈值是需要仔细思考。

2)哈希槽迁移哈希槽在迁移的过程中,无论是单线程环境还是多线程环境,都会造成一个短暂的停止服务过程。这个对生产环境会造成非常短暂的影响我个人认为在服务器特别存储服务器过程中,本来就是面向大量高并发存储,应该可以把哈希槽设置的更加大一些,这样尽可能避免哈希槽的一个迁移。

REDIS哈希存储设计

前面说到的一些场景是一些哈希存储引擎都会面临到的问题,REDIS的解决方面如下:

1)代码层面我觉得REDIS的代码开发者写代码风格真的是太棒了封装性,易看性都是很值得学习的一步一步的看看:

用C写的redis,但是里面有很多STL的那种设计理念:迭代器动态内存管理等

如果你写一个哈希存储,最基本的几个子数据结构是必须的:

每个基本的元素

structDicElement
{
/*data*/
void*key;
void*value;
structDicElement*next;
};

哈希槽

structDicElement**HASHTABLE[HASHSOLT];



这是redis的真实源码,中间用了一个union联合体要么是指针,要么就是一个64位的数字。

typedefstructdictht{

dictEntry**table;
unsignedlongsize;
unsignedlongsizemask;
unsignedlongused;
}dictht;

dictht就是一个完整的哈希槽,这里面记录了table有多少个哈希槽被用了,【used】已经哈希槽有多少个【size】

一般对于静态的哈希存储结构来讲上面2个数据结构就可以了,但是redis有一个特性:就是支持扩容,动态扩容,和stl的vector的策略是相似的当达到临界阈值时,就会增加的到一倍。

真正的dic结果如下:

typedefstructdict{

//这里封装了dic的函数指针结构体典型的C写法如果是c++就是一个类更易读

dictType*type;

void*privdata;

//2个字典一个空一个是需要写入的

dicththt[2];

//如果重新哈希就是扩容这个标记位就会改写

intrehashidx;

intiterators;

}dict;

rehashidx表示正在索引的索引值,字典正在赋值的索引号。

题外话:


如果用C++来写代码片段更加容易看懂。

字典迭代器讨论
typedefstructdictIterator{
//正在迭代的字典
dict*d;
inttable,//是哈希表1还是2
index,//迭代那个哈希槽
safe;
dictEntry*entry,//现在哈希结点
*nextEntry;//后面一个
}dictIterator;

这里的迭代器提出了safe字段:迭代器的安全

迭代器安全:REDIS不是一次性全部迁移过来的,而是根据时间片来迁移,这样的话也就是如果没有迁移完的话,如果有插入迭代器或者删除迭代器存在的话,可能会导致漏掉或者多复制现象存在。

这样的话还是采用最好的战术模式:记录操作这个dic的迭代器数量,只有当全部是安全迭代器时,才可以进行迁移工作。

在生产环境下,如果是HASHTABLE是多线程的呢?多个线程进行读和写,可控制性将会变得非常不可控啊~!而且如果是多线程,一致性怎么能够得到保证呢~!

在每次迁移完ht[i]会释放内存然后制空。没迁移完之前,就会查看2个字典桶。

关于REDIS哈希槽扩容设计

1)每次进行adddel,lookfor操作时,都会做执行dicRehashStep函数一次,在调用dictRehash(d,1)一次,这里的一就是执行rehashidex那个下一个不为null的值一次,也就是把一个槽给迁移到ht[1]中,只执行一次也是为了不会让redis出现太长时间的暂停服务而考虑的一种设计。但是这里的前提就是安全iterator迭代器的数量为0也就是不包含增删改这3个操作的iterator~!如果含有增,删,改,那么有可能会出现漏掉entry的情况。






2)这里是提示用多少毫秒作为一个间隔来做rehash操作,也就是把ht[0]迁移到ht[1]上,每次的base值是100,时间是由服务器来控制,这是第2种迁移方式,这种迁移方式每次迁移的槽多,相对来讲所需要的时间更多,所以ms间隔是需要仔细评估,如果没有弄好,会造成一个时间上的空档。

intdictRehashMilliseconds(dict*d,intms){
longlongstart=timeInMilliseconds();
intrehashes=0;
while(dictRehash(d,100)){
rehashes+=100;
if(timeInMilliseconds()-start>ms)break;
}
returnrehashes;
}


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: