对于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的那种设计理念: 迭代器  动态内存管理 等

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

每个基本的元素

struct DicElement
{
/* data */
void* key;
void* value;
struct DicElement *next;
};

哈希槽

struct DicElement **HASHTABLE[HASHSOLT];

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

typedef struct dictht {

dictEntry **table;     
unsigned long size;    
unsigned long sizemask;
unsigned long used;    
} dictht;

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

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

真正的dic结果如下:

  1. typedef struct dict {

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

  3. dictType *type;

  4. void *privdata;

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

  6. dictht ht[2];

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

  8. int rehashidx;

  9. int iterators;

  10. } dict;

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

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

字典迭代器讨论

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

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

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

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

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

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

关于REDIS哈希槽扩容设计

1) 每次进行add del,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间隔是需要仔细评估,如果没有弄好,会造成一个时间上的空档。

int dictRehashMilliseconds(dict *d, int ms) {
long long start = timeInMilliseconds();
int rehashes = 0;
while(dictRehash(d,100)) {
        rehashes += 100;
if (timeInMilliseconds()-start > ms) break;
    }
return rehashes;
}

 

REDIS 字典数据结构的更多相关文章

  1. Redis 底层数据结构之字典

    文章参考 <Redis 设计与实现>黄建宏 字典 在字典中,每个键都是独一无二的,程序可以在字典中根据键查找与之相关联的值,或者通过键来更新和删除值. 字典在 Redis 中的应用相当广泛 ...

  2. redisbook笔记——redis内部数据结构

    在Redis的内部,数据结构类型值由高效的数据结构和算法进行支持,并且在Redis自身的构建当中,也大量用到了这些数据结构. 这一部分将对Redis内存所使用的数据结构和算法进行介绍. 动态字符串 S ...

  3. redis 字典

    redis 字典 前言 借鉴了 黄健宏 的 <<Redis 设计与实现>> 一书, 对 redis 源码进行学习 欢迎大家给予意见, 互相沟通学习 概述 字典是一种用于存储键值 ...

  4. Redis各种数据结构性能数据对比和性能优化实践

    很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...

  5. 聊一聊Redis的数据结构

    如果没有记错的话,应该是在两个月前把 我们经常看到此类的文章: Redis的五种数据结构 Redis的数据结构以及对应的使用场景 其实以数据结构这个词去说明Redis的String.Hash.List ...

  6. Redis学习——数据结构介绍(四)

    一.简介 作为一款key-value 的NoSQL数据库,Redis支持的数据结构比较丰富,有:String(字符串) .List(列表) .Set(集合) .Hash(哈希) .Zset(有序集合) ...

  7. Redis学习笔记之Redis基本数据结构

    Redis基础数据结构 Redis有5种基本数据结构:String(字符串).list(列表).set(集合).hash(哈希).zset(有序集合) 字符串string 字符串类型是Redis的va ...

  8. 你真的懂redis的数据结构了吗?redis内部数据结构和外部数据结构揭秘

    Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有序集合SortedSet. 很多人面试时都遇到过这种场景吧? 其实除了上面的几种常见数据结构,还需要加上数据结 ...

  9. redis内部数据结构深入浅出

    最大感受,无论从设计还是源码,Redis都尽量做到简单,其中运用到的原理也通俗易懂.特别是源码,简洁易读,真正做到clean and clear, 这篇文章以unstable分支的源码为基准,先从大体 ...

随机推荐

  1. PDF 补丁丁 0.5.0.2731 发布(增加去除页面表单和链接水印功能)

    新的版本增加了简单的删除表单和链接批注的功能,使用该功能可去掉某些软件打上的水印. 在 PDF 文档选项中选中“清除页面所有表单”和“清除页面所有链接批注”项后,程序将会删除页面的表单和链接批注. 效 ...

  2. mysql sql语句大全

    1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份sql server --- 创建 备份 ...

  3. javascript 中的借鸡生蛋

    如题所说,其实指的是 js 中的内置函数和 apply 的结合使用.这种用法很讨巧,读过 jQuery 源码的小伙伴都知道,它在里面被应用的非常广泛和精妙! 梨子:找出一个数组中的最大值和最小值 普遍 ...

  4. js解决IE8、9下placeholder的兼容问题

    由于placeholder是html5的新属性,在IE8.IE9下是不能显示的,有兼容性问题. 解决思路: 1.判断目前浏览器是否支持placeholder属性 2.若不支持,则将type=" ...

  5. JavaScript 闭包系列一

    一. 闭包的概念 闭包是有权访问另一个函数作用域中的变量的函数. 如下代码:根据变量作用域,函数outer中所有的局部变量对函数inner都是可见的.但是反过来不行,inner内部的局部变量对oute ...

  6. Eclipse中的一些快捷键的使用

    Eclipse是一款强大的编程工具,在使用的过程中,若能够有效的使用其快捷键,效率会得到很大的提升,下面是一些常用的eclipse快捷键,可谓是键键精彩. 1.成单词:Alt+/ 2 重构之重命名:S ...

  7. 为什么eclipse中代码提示错误,但是项目目录却不提示错误

    public class Aasf { public static void main(String[] args) { System.out.println("");aihfsa ...

  8. centos添加硬盘

    fdisk -l ## 这里是查看目前系统上有几块硬盘 Disk /dev/sda: 36.4 GB, 36401479680 bytes 255 heads, 63 sectors/track, 4 ...

  9. Struts2中动态方法的调用

    Struts2中动态方法调用就是为了解决一个action对应多个请求的处理,以免action太多. 主要有一下三种方法:指定method属性.感叹号方式和通配符方式.推荐使用第三种方式. 1.指定me ...

  10. 关于CSS三列Float布局任务

    任务目标 掌握HTML/CSS布局的概念 掌握盒模型的概念 掌握position与float的概念以及在布局时的用法 任务描述 使用 HTML 与 CSS 按照示意图;实现三栏式布局. 左右两栏宽度固 ...