一、前言

随着操作的不断执行, 哈希表保存的键值对会逐渐地增多或者减少, 为了让哈希表的负载因子(load factor)维持在一个合理的范围之内, 当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的大小进行相应的扩展或者收缩。

原文解析

二、实现分析

1.rehash过程分析

扩展和收缩哈希表的工作可以通过执行 rehash (重新散列)操作来完成。

Redis 对字典的哈希表执行 rehash 的步骤

1.为字典的 ht[1] 哈希表分配空间, 这个哈希表的空间大小取决于要执行的操作, 以及 ht[0] 当前包含的键值对数量 (也即是ht[0].used 属性的值):

如果执行的是扩展操作, 那么 ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 2^n (2 的 n 次方幂);

如果执行的是收缩操作, 那么 ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n 。

2.将保存在 ht[0] 中的所有键值对 rehash 到 ht[1] 上面: rehash 指的是重新计算键的哈希值和索引值, 然后将键值对放置到 ht[1] 哈希表的指定位置上。

3.当 ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表), 释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备。

结构图解,程序对字典的 ht[0] 进行扩展操作, 步骤如下:

1. ht[0].used 当前的值为 4 , 4 * 2 = 8 , 而 8 (2^3)恰好是第一个大于等于 4 的 2 的 n 次方, 所以程序会将 ht[1] 哈希表的大小设置为 8 。 图 4-9 展示了 ht[1] 在分配空间之后, 字典的样子。

2. 将 ht[0] 包含的四个键值对都 rehash 到 ht[1] , 如图 4-10 所示。

3. 释放 ht[0] ,并将 ht[1] 设置为 ht[0] ,然后为 ht[1] 分配一个空白哈希表,如图 4-11 所示。至此, 对哈希表的扩展操作执行完毕, 程序成功将哈希表的大小从原来的 4 改为了现在的 8

2.哈希表的扩展与收缩

当以下条件中的任意一个被满足时, 程序会自动开始对哈希表执行扩展操作:

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

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

其中哈希表的负载因子可以通过公式计算:

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

比如说, 对于一个大小为 4 , 包含 4 个键值对的哈希表来说, 这个哈希表的负载因子为:

load_factor = 4 / 4 = 1

又比如说, 对于一个大小为 512 , 包含 256 个键值对的哈希表来说, 这个哈希表的负载因子为:

load_factor = 256 / 512 = 0.5

根据 BGSAVE 命令或 BGREWRITEAOF 命令是否正在执行, 服务器执行扩展操作所需的负载因子并不相同, 这是因为在执行 BGSAVE 命令或BGREWRITEAOF 命令的过程中, Redis 需要创建当前服务器进程的子进程, 而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率, 所以在子进程存在期间, 服务器会提高执行扩展操作所需的负载因子, 从而尽可能地避免在子进程存在期间进行哈希表扩展操作, 这可以避免不必要的内存写入操作, 最大限度地节约内存。

另一方面, 当哈希表的负载因子小于 0.1 时, 程序自动开始对哈希表执行收缩操作。

注释:写时复制(copy-on-write)是一种可以推迟甚至避免复制数据的技术。内核此时并不是复制整个进程空间,而是让父进程和子进程共享同一个副本。只有在需要写入的时候,数据才会被复制,从而使父进程、子进程拥有各自的副本。也就是说,资源的复制只有在需要写入的时候才进行,在此之前以只读方式共享。

3.渐进式 rehash

扩展或收缩哈希表需要将 ht[0] 里面的所有键值对 rehash 到 ht[1] 里面, 但是, 这个 rehash 动作并不是一次性、集中式地完成的, 而是分多次、渐进式地完成的。

这样做的原因在于, 如果 ht[0] 里只保存着四个键值对, 那么服务器可以在瞬间就将这些键值对全部 rehash 到 ht[1] ; 但是, 如果哈希表里保存的键值对数量不是四个, 而是四百万、四千万甚至四亿个键值对, 那么要一次性将这些键值对全部 rehash 到 ht[1] 的话, 庞大的计算量可能会导致服务器在一段时间内停止服务。

因此, 为了避免 rehash 对服务器性能造成影响, 服务器不是一次性将 ht[0] 里面的所有键值对全部 rehash 到 ht[1] , 而是分多次、渐进式地将 ht[0] 里面的键值对慢慢地 rehash 到 ht[1] 。

以下是哈希表渐进式 rehash 的详细步骤:

1.为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。

2.在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。

3.在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。

4.随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。

渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。

图 4-12 至图 4-17 展示了一次完整的渐进式 rehash 过程, 注意观察在整个 rehash 过程中, 字典的 rehashidx 属性是如何变化的。









描述

因为在进行渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht1 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht1 里面进行查找, 诸如此类。

另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht1 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。

三、要点总结

1.字典使用哈希表作为底层实现, 每个字典带有两个哈希表, 一个用于平时使用, 另一个仅在进行 rehash 时使用

2.当哈希表保存的键值对数量太多或者太少时, 程序需要对哈希表的大小进行相应的扩展或者收缩(rehash)

3.rehash 动作并不是一次性、集中式地完成的, 而是分多次、渐进式地完成的

4.渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht[1] 两个哈希表

《闲扯Redis八》Redis字典的哈希表执行Rehash过程分析的更多相关文章

  1. Redis源码研究:哈希表 - 蕫的博客

    [http://dongxicheng.org/nosql/redis-code-hashtable/] 1. Redis中的哈希表 前面提到Redis是个key/value存储系统,学过数据结构的人 ...

  2. Redis常用操作-------Hash(哈希表)

    1.HDEL key field [field ...] 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略. 在Redis2.4以下的版本里, HDEL 每次只能删除单个域,如果你需要在一 ...

  3. redis命令之 ----Hash(哈希表)

    HDEL HDEL key field [field ...] 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略. HEXISTS HEXISTS key field 查看哈希表 key  ...

  4. Redis Hlen 命令用于获取哈希表中字段的数量

    http://www.runoob.com/redis/hashes-hlen.html

  5. Redis哈希表总结

    本文及后续文章,Redis版本均是v3.2.8 在文章<Redis 数据结构之dict><Redis 数据结构之dict(2)>中,从代码层面做了简单理解.总感觉思路的不够条理 ...

  6. Redis实现之字典

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

  7. redis中的字典结构是怎样的?

    点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 基础概念 redis支持的5种数据类型中,有hash类型,hash类型的 ...

  8. Redis原理再学习04:数据结构-哈希表hash表(dict字典)

    哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...

  9. Redis学习之字典源码分析

    字典,又叫映射,是一种用于保存键值对的抽象数据结构 划重点:抽象数据结构 Redisd字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表结点,而每个哈希表结点就保存了字典中的一个键值对 一.哈 ...

随机推荐

  1. 安装hadoop2.9.2 jdk1.8 centos7

    安装JDK1.8 查看JDK1.8的安装 https://www.cnblogs.com/TJ21/p/13208514.html 安装hadoop 上传hadoop 下载hadoop     地址h ...

  2. 关于 charset 的几种编码方式

    经常遇到charset=gb2312.charset=iso-8859-1.charset=utf-8这几种编码方式,它们有什么不同,看下面的图 编码方式 含义 charset=iso-8859-1 ...

  3. CSS布局之盒子模型[一]

    每个HTML标签都会生成一个盒模型,盒模型是正常流布局非常重要的概念.盒模型由内边距(padding)+长度(width)+高度(height)+边框(border)+外边距(margin)组成. 1 ...

  4. Spring FactoryBean 缓存

    相关文章 Spring 整体架构 编译Spring5.2.0源码 Spring-资源加载 Spring 容器的初始化 Spring-AliasRegistry Spring 获取单例流程(一) Spr ...

  5. 如何用好 IDEA ,Java 撸码效率至少提升 5 倍?

    以前的Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString:异常处理:I/O流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok应运 ...

  6. eIQ WSL下工具及环境配置

    1. 配置WSL 参考[https://www.cnblogs.com/hayley111/p/12844337.html] 2. 配置VScode 参考[https://zhuanlan.zhihu ...

  7. 常用的rac搭建相关

    平时自己测试环境搭建用,部分参数是不规范的. 生产请按照官方文档或者公司标准化文档来做. 共享硬盘: disk.locking = "FALSE" diskLib.dataCach ...

  8. (私人收藏)红色项目工作计划总结汇报PPT模板

    红色项目工作计划总结汇报PPT模板 https://pan.baidu.com/s/1fr-PB7ieveSIqSXrB0YXsgbgdc

  9. Spring IoC bean 的初始化

    前言 本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本.因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析. 本篇文章主要介绍 Spring IoC 容 ...

  10. 状压DP之中国象棋

    题目 传送们 这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法.大家肯定很清楚,在中国象棋中炮的行走方 ...