Redis 的字典底层使用哈希表实现,说到哈希表大家应该能联想到 HashMap 或者是 Hashtable,也应该能联想到 key、value 的存储形式,以及哈希表扩容,哈希算法等知识点。那么 Redis 字典是否也是通过这种形式实现的呢?带着这些疑问下面我们就来了解一下 Redis 中的哈希表。

一、 哈希表结构

  • table:用于存储键值对
  • size:表示哈希表的数组大小
  • used:表示哈希表中已经存储的键值对的个数
  • sizemask:大小永远为 size - 1,该属性用于计算哈希值

二、 字典结构

上面我们说过字典是基于哈希表实现的,通过上图我们可以看出字典包含了 2 个哈希表,还有一些其他属性,比如 rehashindextype 等。

为什么字典使用 2 个哈希表作为底层实现呢?原因是与 rehash 相关,与 rehash 相关的还有 rehashindex 属性,下面我们会具体看到字典 rehash 的过程。

三、哈希算法

当我们把一个新的键值对添加到字典里时,会先根据键的哈希值计算其对应的索引值,然后根据索引值,将新的键值对放到哈希表数组指定的索引上面。

如果当 2 个或 2 个以上的键值对被分配同一个索引上面时,就发生了哈希冲突。我们联想下 HashMap 中时如何解决哈希冲突的呢?HassMap 会通过链地址法将新的键值对通过链表的形式追加在上一个键值对后面。Redis 中的字典也是通过链地址法解决哈希冲突的,不过有一点不同的是 Redis 会将新添加的键值对放在链表的头节点位置上。

四、rehash

4.1 负载因子

我们再来想一下 HashMap 中的 rehash,HashMap 中有一个 threshold 字段,这个字段在作为扩容阈值时默认情况下为 0.75 * capacity,意思是当哈希表中键值对的数量达到哈希表容量的 0.75 倍时就需要对哈希表进行扩容。

Redis 哈希表的负载因子通过下面的公式计算:

 # 负载因子 = 哈希表已保存节点数量 / 哈希表大小load_factor = ht[0].used / ht[0].size

4.2 rehash 条件

Redis 哈希表不仅提供了扩容还提供了收缩机制,扩容与收缩都是通过 rehash 完成的。与 HashMap 一样,Redis 中的哈希表想要执行 rehash 扩容操作也是需要一定条件的,主要为以下 2 个:

  • 服务器目前没有执行 BGREWRITEAOF 或者 BGSAVE 命令,切哈希表的负载因子大于等于 1
  • 服务器目前正在执行 BGSAVE 或者 BGREWRITEAOF 命令, 并且哈希表的负载因子大于等于 5

下面是收缩 rehash 的条件:

  • 哈希表的负载因子小于 0.1 时, 程序自动开始对哈希表执行收缩操作

上面扩容时根据 BGREWRITEAOF 或者 BGSAVE 命令是否执行分了两种情况,为什么要这么做呢?原因如下:

在执行 BGREWRITEAOF 或者 BGSAVE 命令 时,Redis 会为当前服务器进程创建一个子进程,所以在子进程存在期间,会提高执行扩容的负载因子,因为这样可以避免在子进程存在期间进行哈希表的扩容操作,从而避免不必要的内存写入操作,最大限度的节约内存,提高效率。

4.3 rehash 扩容过程

Redis 字典 rehash 过程比较有意思的是它通过 2 个哈希表实现,当没有在 rehash 时:rehashidx 的值为 -1,且使用哈希表 0 存储键值对,哈希表 1 什么也不存储。

rehash 过程:

  • 为字典的 ht[1] 哈希表分配空间,分配的大小如下

    • 扩容:ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 2^n
    • 收缩:ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n
  • 将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上,这个过程会重新计算键的哈希值和索引值, 然后将键值对放置到 ht[1] 哈希表的指定位置上
  • ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表), 释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备

下面是 rehash 前后的一个对比

上面只给出了最终的结果对比,其实在 rehash 的过程中,每当一个键值对被 rehash 到 ht[1]
上时,对应的 rehashidx 属性就会加 1。

4.4 渐进式 rehash

上面我们提到 Redis 字典的 rehash 过程,其实 rehash 并不是一次性,集中式的完成的,而是分多次,渐进式完成的。原因是 Redis 的字典字典有可能存储上百万个键值对,如果一次性完成的话,那么 Redis 可能会在一段时间内停止服务,为了保证 Redis 的高性能,这么做肯定是不允许的。

4.5 扩展问题分析

上面我们已经知道 rehash 的过程,现在我们来思考一个问题,在 rehash 的过程中,如果我们对字典进行了增删改查,那么会操作哪个哈希表呢?是旧的还是新的,下面我们就来分析下:

其实不管执行任何操作,都不会允许有键值有丢失或者不一致的情况,有了这个前提后再进行分析就比较简单了。在新增键值对的时候肯定会添加到新的哈希表中去,因为添加到旧的哈希表的话,最终还是被 rehash 到新的哈希表,就没有必要进行一次浪费的 rehash 了。

删改查操作在保证一致性的前提下,一定会先操作旧的哈希表,如果在旧的哈希表中没有操作成功,会继续操作新的哈希表。我们想一下,如果删改查先操作新的哈希表再操作旧的哈希表的话,那么在操作的过程中可能有一部分数据被 rehash 到新的哈希表中去,这些数据有可能因为重新哈希的原因而被忽略。

五、参考资料

《Redis 设计与实现》 黄健宏著

Redis 数据结构之哈希表相关推荐

  1. 「Redis数据结构」哈希表(Dict)

    「Redis数据结构」哈希表(Dict) 文章目录 「Redis数据结构」哈希表(Dict) @[toc] 一.概述 二.结构 三.哈希冲突 四.链式哈希 五.rehash 六. 渐进式 rehash ...

  2. 「Redis数据结构」哈希对象(Hash)

    「Redis数据结构」哈希对象(Hash) 文章目录 「Redis数据结构」哈希对象(Hash) 一.概述 二.编码 ZipList HashTable 三.编码转换 一.概述 Redis中hash对 ...

  3. 用c语言实现基本数据结构(哈希表)

    用c语言实现基本数据结构(哈希表) 写这个哈希表总是段错误,找了半天的bug...原来是各种小错误不断,写得很蛋疼. 我是是用数组实现的,数组的最大值定义成的宏.一共只有四个函数,分别为初始化哈希表, ...

  4. C++八股文分享---数据结构其二---哈希表

    C++八股文分享-数据结构其二-哈希表 前言 什么是哈希表? 搜索二叉树对值的查找是通过从根节点开始,逐个节点与目标值做比较,向下查找,直至找到目标值或是到达根节点未查找到,时间复杂度为O(logn) ...

  5. 图书馆管理系统(C、数据结构、哈希表、文件IO)

    目录 技术路线 实现效果展示 ​程序主体 1.头文件部分 2.自定义函数定义部分 3.main函数部分 注意 技术路线 数据结构.哈希表.文件IO 通过C语言进行程序设计,有用到数据结构中的哈希表,通 ...

  6. 【数据结构】哈希表的概念及应用

    [数据结构]哈希表的概念及应用 前言 1.写出哈希表的基本概念 2.列出常用的哈希函数构造方法,并阐述各自的特点. 直接定址法 除留余数法 数字分析法 其他构造整数关键字的哈希函数的方法: 平方取中法 ...

  7. 【数据结构之哈希表(二)】 哈希表的扩容实现机制

    哈希表的扩容实现机制 导语 哈希表 什么是哈希表 装载因子 Hashcode 哈希冲突 扩容方案 Java中的实现 Redis中的实现 结束语 导语 哈希表是实际开发中非常常用的数据结构,也很重要.了 ...

  8. python连接Redis,学习哈希表基本操作

    需要redis安装的请看网址 (https://www.runoob.com/redis/redis-install.html) python操作redis的第三方库叫"redis-py&q ...

  9. j - 数据结构实验:哈希表_一看就懂的数据结构基础「哈希表」

    哈希表 哈希表(Hash table),是存储键值(Key Value)对数据的一种数据结构. 例如,我们可以将人的名字作为键,性别作为值来存储.通过把键映射到表中的一个位置来访问数据,以提高查找速度 ...

最新文章

  1. WINDOWS 2008 脱机文件夹
  2. 区块链技术没那么复杂,别被大佬们忽悠晕了
  3. lvs在linux系统下安装,Linux下安装lvs
  4. LeetCode-53. 最大子序和-最简单的动态规划(Python3)
  5. MMC 不能打开文件
  6. dart系列之:dart语言中的异常
  7. [转]ActiveX控件安全初始化之一:实现ISafeObject接口
  8. 阿里安全开源顶尖技术“猎豹” 计算更快数据更安全
  9. 果皇的矩阵[matrix]
  10. 53 岁张亚勤官宣:正式加入清华!
  11. C# 窗口大小及屏幕分辨率操作
  12. 2.跳转到指定的位置
  13. OpenQA.Selenium.WebDriverException : Failed to start up socket within 45000
  14. 工作中常用的25个Excel操作技巧,附详细步骤,收藏备用
  15. Java类加载器的使用
  16. Python实现修正cholesky分解
  17. dw中html是什么,dw中的css是什么意思?
  18. 【读书笔记】《M型社会》大前研一
  19. Windows(10) Python polyglot安装和运行失败的问题
  20. 建立“图书_读者”数据库及如下 3 个表,并输入实验数据,用 SQL 语句实现如下五个查询(opengauss)

热门文章

  1. Thymeleaf——使用模板动态生成JavaScript脚本文件
  2. Spring Boot——游戏成就系统设计DEMO
  3. 《Java程序设计》实验报告——Java的接口与继承
  4. mysql 表大小_MySQL查看数据库表容量大小的方法示例
  5. Maven-学习笔记02【基础-Maven的安装和仓库种类】
  6. 求二叉树最长路径长度和
  7. iOS中代码支持多国语言切换的实现(Xcode5+iOS7)
  8. Bugku—MISC题总结
  9. Windows服务器修改默认TTL值的方法
  10. Java操作数据库Sqlite样例代码