点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。

文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。

基础概念

redis支持的5种数据类型中,有hash类型,hash类型的底层采用字典结构(多对key-value)实现,而字典结构的代码实现=hashTable=用到了hash表

字典结构的实现

字典结构由三种结构组合而成:字典结构=dict+dictht+dictEntry,关系如下:

代码实现:

typedef struct dict {
dictType *type; //dictType也是一种数据结构,dictType结构中包含了一些函数,这些函数用来计算key的哈希值,进而用这个哈希值计算key在dictEntry型table数组中的下标
void *privdata; //私有数据,保存着dictType结构中函数的参数
dictht ht[2]; //两张哈希表:一张用来正常存储节点,一张用来在rehash时临时存储节点
long rehashidx; //rehash的标记:默认-1,当table数组中已有元素个数增加/减少到一定量时,整个字典结构将进行rehash给每个table元素重新分配位置,rehashidx代表rehash过程的进度,rehashidx==-1代表字典没有在进行rehash,rehashidx>-1代表该字典结构正在对进行rehash
} dict; typedef struct dictht { //哈希表
dictEntry **table; //存放一个数组的地址,每个数组元素存放的是哈希表节点dictEntry的地址
unsigned long size; //哈希表的table数组长度小,初始化大小为4
unsigned long sizemask; //哈希表掩码sizemask的值总是等于(size-1),用于计算每个key在table中的下标位置=hash(key)&sizemask
unsigned long used; //记录哈希表的table中已有的节点数量(节点=dictEntry=键值对)。
} dictht; typedef struct dictEntry {
void *key;//键
union{ //值
void *val;//值可以是指针
uint64_tu64;//值可以是无符号整数
int64_ts64;//值可以是带符号整数
} v;
struct dicEntry *next;//指向下个dictEntry节点:redis的字典结构采用链表法解决hash冲突,当table数组某个位置处已有元素时,该位置采用头插法形成链表解决hash冲突
} dictEntry;

用key计算(key-value)在table中的位置下标:

//1、先计算key的hash值:使用字典中计算key哈希值的函数
hash = dict结构->dictType结构的函数hashFunction(key)
//2、根据hash值与哈希表掩码sizemask进行“与运算”得到下标,x=(0或1)=两张哈希表中用来正常存储节点的那张哈希表的下标
index = hash & dict->ht[x].sizemask;

字典中的负载因子及rehash

先来看看几个重要概念:

redis字典中哈希表的rehash=扩展或收缩哈希表中的table数组长度;

负载因子=dict结构的ht[0].used/dict结构的ht[0].size=哈希表的table中已有的节点数量/哈希表的table数组长度;

bgsave操作:将redis内存中的数据以rdb的形式持久化到磁盘;

bgrewriteaof操作:将redis内存中的数据以aof的形式持久化到磁盘中,持久化成功后旧的aof文件会被替换(redis2.4之后aof由redis自动触发,而bgrewriteaof需要手动地触发)

redis中的哈希表什么时候进行扩展操作?

两种情况:

当没有bgsave操作 && 没有bgrewriteaof操作 && 负载因子>=1时;

当(正在bgsave操作 || 正在bgrewriteaof操作) && 负载因子>=5时;

负载因子为什么会>=1?因为当hash冲突时,新节点会以头插法的形式插入哈希表的table数组某个位置中形成链表,就会使table中节点的总数量>table数组长度,进而负载因子>=1

redis中的哈希表什么时候进行扩展操作?

当负载因子<=0.1时(如table数组长度经过多次扩展变为了16,某时刻table只有1个元素,1/16=0.0625<0.1,则哈希表进行rehash收缩table长度)

redis中的哈希表进行扩展或缩收缩的过程?

扩展:dict数据结构中有两张哈希表ht[2],ht[0]拿来正常地装redis数据(key-value),ht[1]用来进行rehash扩展时存放ht[0]的元素,直到ht[0]中所有元素被重新计算下标存放到ht[1]中的table数组为止,ht[1]的大小=第一个大于等于ht[0].used的2^n

收缩:同"扩展"操作一样,ht[1]拿来装在收缩过程中ht[0]的元素,ht[1]的大小=第一个大于等于ht[0].used的2^n(公式与扩展一样)

特点:

  • 扩展或收缩完成后,释放哈希表ht[0],并把哈希表ht[1]置为ht[0],然后再重新分配一个新的空白ht[1]作为下次rehash使用
  • 在rehash(扩展或收缩)过程中有两张哈希表,并且字典会同时使用两张哈希表:查找、删除、更新会同时操作两张哈希表(先在ht[0]中找,找不到再去ht[1]中找),'插入'则只操作ht[1]
  • rehash过程是渐进式的,它采取分而治之的方法,以扩展为例,一开始先将rehashidx值由-1置为0代表rehash工作开始:

    此时rehashidx为0,第一次对redis字典进行【添加、删除、查找或者更新】操作,则对ht[0]中table[rehashidx]=table[0]的数据重新计算下标放到ht[1]的table数组中,然后rehashidx自增为1;

    此时rehashidx为1,第二次对redis字典进行【添加、删除、查找或者更新】操作,则对ht[0]中table[rehashidx]=table[1]的数据重新计算下标放到ht[1]的table数组中,然后rehashidx自增为1

    ......以此类推,直到ht[0]中的所有table元素都被重新计算下标rehash到ht[1]中为止,最后把rehashidx置为-1,此时rehash完成;

渐进式rehash可以把这个过程中的计算压力分摊到每次对字典进行【添加、删除、查找或者更新】操作的时候,避免在某一时刻对整个hash类型数据(redis的5中数据类型之一)进行庞大的rehash计算,进而避免了redis阻塞,唯一不好的地方就是在rehash时同时使用两个哈希表,导致redis内部使用量暴增

OK,如果文章哪里有错误或不足,欢迎各位留言。

创作不易,各位的「三连」是二少创作的最大动力!我们下期见!

redis中的字典结构是怎样的?的更多相关文章

  1. 快速整明白Redis中的字典到底是个啥

    字典简介 字典是一种用于保存键值对的数据结构,可以通过键值对中的键快速地查找到对应的值.在Redis所使用的C语言中,并没有内置字典,所以Redis自己实现了字典. 整个Redis数据库的所有的键和值 ...

  2. Redis数据结构详解(2)-redis中的字典dict

    前提知识 字典,又被称为符号表(symbol table)或映射(map),其实简单地可以理解为键值对key-value. 比如Java的常见集合类HashMap,就是用来存储键值对的. 字典中的键( ...

  3. Redis核心原理与实践--散列类型与字典结构实现原理

    Redis散列类型可以存储一组无序的键值对,它特别适用于存储一个对象数据. > HSET fruit name apple price 7.6 origin china 3 > HGET ...

  4. Redis实现之字典

    字典 字典,又称为符号表(symbol table).关联数组(associative array)或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构.在字典中,一个 ...

  5. 《闲扯Redis十》Redis 跳跃表的结构实现

    一.前言 Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要. ...

  6. Redis 中的 set 和 sorted set 如何使用,源码实现分析

    set 和 sorted set 前言 set 常见命令 set 的使用场景 看下源码实现 insert dict sorted set 常见的命令 使用场景 分析下源码实现 ZADD ZRANGE ...

  7. 《闲扯Redis七》Redis字典结构的底层实现

    一.前言 上节<闲扯Redis六>Redis五种数据类型之Hash型 中说到 Hash(哈希对象)的底层实现有: 1.ziplist 编码的哈希对象使用压缩列表作为底层实现 2.hasht ...

  8. Redis中5种数据结构的使用场景介绍

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/108.html?1455861435 一.redis 数据结构使用场景 原 ...

  9. 深入理解Redis中的主键失效及其实现机制

    参考:http://blog.sina.com.cn/s/articlelist_1221155353_0_1.html 作为一种定期清理无效数据的重要机制,主键失效存在于大多数缓存系统中,Reids ...

随机推荐

  1. 《HelloGitHub》第 72 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  2. IO、NIO和AIO的区别

      IO和NIO的区别:其本质就是阻塞和非阻塞的区别. 阻塞概念:应用程序在获取网络数据的时候,如果网络传输数据很慢,那么久一直等着,知道传输完毕为止.非阻塞概念:应用程序直接可以获取已经准备就绪好的 ...

  3. python 面向对象的一些魔法方法和反射

    1.with和__enter__,__exit__,__init__配合使用class A: def __init__(self): print('init') def __enter__(self) ...

  4. Ajax是什么?包含什么技术?有什么作用?

    Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的缩写. Ajax 尝试建立桌面应用程序的功能和交互性,与不断更新的 Web 应用程序之间的桥梁.不 ...

  5. @Required 注解?

    这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationExce ...

  6. CHAR 和 VARCHAR 的区别?

    1.CHAR 和 VARCHAR 类型在存储和检索方面有所不同 2.CHAR 列长度固定为创建表时声明的长度,长度值范围是 1 到 255 当 CHAR 值被存储时,它们被用空格填充到特定长度,检索  ...

  7. jQuery--文档处理案例

    需求 如上图,实现左右两边的选择菜单可以左右移动,'>'按钮一次只能移动被选中的一个菜单,'>>'按钮一次移动所有被选择的菜单,'>>>'按钮 将所有菜单进行移动, ...

  8. 为什么要配置JDK环境变量?

    1. PATH环境变量.作用是指定命令搜索路径,在shell下面执行命令时,它会到PATH变量所指定的路径中查找看是否能找到相应的命令程序.我们需要把 jdk安装目录下的bin目录增加到现有的PATH ...

  9. 学习heartbeat-03t实现web服务的高可用案例及维护要点

    8.Heartbeat实现web服务的高可用案例 8.1部署准备 通过web服务高可用案例来熟悉heatbeat软件的使用,用上面的两台服务器机器名分别为heartbeat-1-130和heartbe ...

  10. 遇到的问题之“解决tomcat中文乱码问题”

    方案1:在server.xml中添加了 URIEncoding="UTF-8" 属性 路径:C:\stop\apache-tomcat-8.5.69\conf 修改内容 如若方案1 ...