Redis rehash 相关问题

前言

本文主要介绍 Redis Hash 表 rehash 相关的三个问题:

  • 什么时候触发 rehash
  • rehash 扩容扩多大
  • rehash 如何执行

介绍的源码基于 Redis 5.0.8 版本,会删除一些不影响理解的部分。

什么时候触发 rehash

Redis 用于判断是否触发 rehash 的函数是 _dictExpandIfNeeded。它的源码如下:

static int _dictExpandIfNeeded(dict *d) {if (dictIsRehashing(d)) {return DICT_OK;}// 对应初始化场景,不属于 rehash 操作if (d->ht[0].size == 0) {return dictExpand(d, DICT_HT_INITIAL_SIZE);}if (d->ht[0].used >= d->ht[0].size &&(dict_can_resize ||d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)) {return dictExpand(d, d->ht[0].used*2);}return DICT_OK;
}

从上述代码可以看到有两个扩容条件:

  1. Hash 表可以进行扩容,同时 ht[0] 的负载因子大于等于 1
  2. Hash 表不可以进行扩容,但 ht[0] 的负载因子大于 dict_force_resize_ratio(默认值为 5)

dict_can_resize 这个变量由以下两个函数设置:

void dictEnableResize(void) {dict_can_resize = 1;
}void dictDisableResize(void) {dict_can_resize = 0;
}

上面两个函数被 updateDictResizePolicy 函数调用,启用扩容的条件是:当前没有 RDB 子进程,也没有 AOF 子进程,即没有在执行 BGSAVE 命令或 BGREWRITEAOF 命令。

void updateDictResizePolicy(void) {if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {dictEnableResize();} else {dictDisableResize();}
}

为什么有子进程时要提高执行扩展所需的负载因子呢?

这是因为目前大多数操作系统都采用写时复制技术来优化子进程的使用效率,这样可以避免不必要的内存写入操作。

_dictExpandIfNeeded 被 _dictKeyIndex 调用,而 _dictKeyIndex 又被 dictAddRaw 调用。

rehash 扩容扩多大

在 Redis 中,扩容是通过调用 dictExpand 函数完成的。上面的调用中有如下调用:

dictExpand(d, d->ht[0].used*2);

是将当前已使用大小的 2 倍传给 dictExpand 函数,dictExpand 代码如下:

int dictExpand(dict *d, unsigned long size) {// 新的哈希表dictht n; unsigned long realsize = _dictNextPower(size);// 申请空间并进行初始化n.size = realsize;n.sizemask = realsize-1;n.table = zcalloc(realsize*sizeof(dictEntry*));n.used = 0;d->ht[1] = n;d->rehashidx = 0;return DICT_OK;
}

上面的代码调用 _dictNextPower 获取了 realsize。如果执行的是扩展操作,那么 realsize 的大小为第一个大于等于 dt[0].used * 2 的 2 n 2^n 2n

为什么要扩容为之前的 2 倍,并且是 2 n 2^n 2n 的倍数呢?这样做有两方面好处:

  1. 可以将 hash % n 操作转换为 hash & (n - 1),取模转换为位运算操作,提高效率
  2. 对元素的重新分配操作更简单,只需要根据 hash & oldCap 是否等于 0 拆分成两部分。等于 0 的 newTable[i] = oldTable[i];不等于 0 的 newTable[i + oldCap] = oldTable[i]

渐进式 rehash 如何执行

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

这样做的原因在于,如果哈希表里保存的键值对数量很多,那么要一次性将这些键值对全部 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 属性的值加 1
  4. 随着字典操作的不断进行,ht[0] 上的所有键值对都会被 rehash 到 ht[1],这时程序将 rehashidx 属性的值设为 -1,表示 rehash 操作已完成

rehash 在代码层面是如何实现的呢?有两个关键函数:dictRehash 和 _dictRehashStep。下面是 dictRehash 的代码,它将 n 个桶的元素移动到 ht[1]:

int dictRehash(dict *d, int n) {// 要访问的空桶的最大数量int empty_visits = n*10;while(n-- && d->ht[0].used != 0) {dictEntry *de, *nextde;// 当前桶为空,访问下一位置while(d->ht[0].table[d->rehashidx] == NULL) {d->rehashidx++;if (--empty_visits == 0) {return 1;}}// 待移动的桶de = d->ht[0].table[d->rehashidx];// 将该桶中的元素从 ht[0] 移动到 ht[1]while(de) {uint64_t h;nextde = de->next;// 获取在 ht[1] 的下标h = dictHashKey(d, de->key) & d->ht[1].sizemask;de->next = d->ht[1].table[h];d->ht[1].table[h] = de;d->ht[0].used--;d->ht[1].used++;de = nextde;}d->ht[0].table[d->rehashidx] = NULL;d->rehashidx++;}// 检查是否已经完成了 rehashif (d->ht[0].used == 0) {zfree(d->ht[0].table);d->ht[0] = d->ht[1];_dictReset(&d->ht[1]);d->rehashidx = -1;return 0;}// 还有更多需要 rehashreturn 1;
}

_dictRehashStep 函数实现了对一个 bucket 执行 rehash。一共有 5 个函数调用 _dictRehashStep 函数,分别是:dictAddRaw、dictGenericDelete、dictFind、dictGetRandomKey 和 dictGetSomeKeys。

那么此时问题来了,如果我们将 ht[0] 中 0、1、2 号下标 bucket 中的元素移动到了 ht[1],后续又往这几个地方插入该怎么办?

不用担心,因为在 rehash 时,会用如下代码判断要操作的哈希表,所以不会有上述问题。

ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];

查找和删除操作会检查两个哈希表,所以也没有任何问题。

参考资料

  • 《Redis 设计与实现》
  • 极客时间:Redis 源码剖析与实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/9572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

‘pip‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

因为python13不支持 pip install cx_Oracle,卸载了python13,重新安装python10,导致cmd命令不识别 pip ,和python10,(;༎ຶД༎ຶ) 记录一种临时方案 如果你的命令行界面(cmd)不识别pip命令&am…

如何挑选“好用”的工业APP

我们日常生活中每天都在使用各种生活类的APP,然而,当我们谈到工业APP时,很多人可能并不那么熟悉。工业APP,虽然不像生活类APP那样直接面向广大消费者,但在工业领域却扮演着至关重要的角色。 先简单认识下啥是工业APP? 工业APP是…

集成学习算法:AdaBoost原理详解以及基于adaboost的图像二分类代码实现

本文尽量从一个机器学习小白或是只对机器学习算法有一个大体浅显的视角入手,尽量通俗易懂的介绍清楚AdaBoost算法! 一、AdaBoost简介 AdaBoost,是英文"Adaptive Boosting"(自适应增强)的缩写,由…

【退役之重学Java】关于缓存

一、为什么要用缓存 缓存嘛,对比计算机组成原理中的“高速缓存控制器”,就可以知道,缓存的存在是为了获取高性能,特别是在高并发场景下获取高性能。 二、缓存是如何获取高性能的 SQL的执行是非常消耗性能的有一些SQL经常是一样的…

【开发工具】使用Github pages、Hexo如何10分钟内快速生成个人博客网站

文章目录 一.准备工作1.安装git2.安装node安装 cnpm 3.使用 GitHub 创建仓库,并配置 GitHub Pages0.Github Pages是什么1. 在 GitHub 上创建一个新仓库2. 创建您的静态网站3. 启用 GitHub Pages4. 等待构建完成5. 访问您的网站 二. Hexo1.什么是Hexo2.安装Hexo1. 安…

分拣机器人也卷的飞起来了

导语 大家好,我是智能仓储物流技术研习社的社长,老K。专注分享智能仓储物流技术、智能制造等内容。 新书《智能物流系统构成与技术实践》 智能制造-话题精读 1、西门子、ABB、汇川:2024中国工业数字化自动化50强 2、完整拆解:智能…

解锁Swagger鉴权

在开发过程中,Swagger 是一个非常流行的 API 文档生成工具,它不仅可以帮助开发者设计、构建、记录 RESTful API,还能通过其交互式的 UI 改善前后端开发者的沟通效率。然而,在实际生产环境中,暴露未加保护的 API 文档可…

大数据基础工程技术团队4篇论文入选ICLR,ICDE,WWW

近日,由阿里云计算平台大数据基础工程技术团队主导的四篇时间序列相关论文分别被国际顶会ICLR2024、ICDE2024和WWW2024接收。 论文成果是阿里云与华东师范大学、浙江大学、南京大学等高校共同研发,涉及时间序列与智能运维结合的多个应用场景。包括基于P…

Android 蓝牙实战——蓝牙电话通话状态同步(二十四)

前面分析了蓝牙电话通话状态的广播,我们可以在蓝牙电话中实时监听蓝牙电话的状态,但如果是其他音乐类 APP 呢,在播放的时候也需要知道当前是否有通话正在进行,但是有完全没必要实时监听电话的状态,这就需要一个获取通话状态的方法。 一、通话状态处理 1、CallsManager …

MySQL学习笔记12——效率和优化

效率和优化 一、对查询语句进行调优1、查询分析语句2、优化查询方法 二、改进表设计以提高性能1、优化数据类型2、合理增加冗余字段以提高效率3、拆分表4、使用非空约束 三、如何充分利用系统资源1、优化系统资源配置2、如何利用系统资源来诊断问题 一、对查询语句进行调优 你…

vscode触发建议缓慢问题

说明 关于vscode上vite项目文件过多导致触发建议缓慢问题, 本人框架主要使用的技术是 vite vue3 ts tailwind eslint 项目初始阶段建议提示秒出,当项目文件过多时,建议延迟太高,即使是console代码片段也会过好几秒才出现 …

【R语言】边缘概率密度图

边缘概率密度图是一种在多变量数据分析中常用的图形工具,用于显示每个单独变量的概率密度估计。它通常用于散点图的边缘,以便更好地理解单个变量的分布情况,同时保留了散点图的相关性信息。 在边缘概率密度图中,每个变量的概率密度…

react native 设置屏幕锁定

原生配置 android 在android/app/src/main/AndroidManifest.xml在这个文件里的入口activity里添加 android:screenOrientation"portrait" <activityandroid:name".MainActivity"android:label"string/app_name" …

数字工厂管理系统如何助力企业数据采集与分析

随着科技的不断进步&#xff0c;数字化已成为企业发展的重要趋势。在制造业领域&#xff0c;数字工厂管理系统的应用日益广泛&#xff0c;它不仅提升了生产效率&#xff0c;更在数据采集与分析方面发挥着举足轻重的作用。本文旨在探讨数字工厂管理系统如何助力企业数据采集与分…

JavaScript异步编程——06-Promise入门详解【万字长文,感谢支持】

前言 Promise 是 JavaScript 中特有的语法。可以毫不夸张得说&#xff0c;Promise 是ES6中最重要的语法&#xff0c;没有之一。初学者可能对 Promise 的概念有些陌生&#xff0c;但是不用担心。大多数情况下&#xff0c;使用 Promise 的语法是比较固定的。我们可以先把这些固定…

【Linux】冯诺依曼体系

冯诺依曼体系 冯诺依曼体系结构是我们计算机组成的基本架构 中央处理器&#xff08;CPU&#xff09;&#xff1a; 中央处理器是冯诺伊曼体系的核心部分&#xff0c;负责执行计算机程序中的指令。它包括算术逻辑单元&#xff08;ALU&#xff09;和控制单元&#xff08;CU&#x…

uniapp 自定义App UrlSchemes

需求&#xff1a;外部浏览器H5页面&#xff0c;跳转到uniapp开发的原生app内部。 1、uniapp内部的配置&#xff1a; &#xff08;1&#xff09;打开manifest->App常用其他设置&#xff0c;如下&#xff0c;按照提示输入您要设置的urlSchemes&#xff1a; &#xff08;2&am…

Flink面试整理-Flink集群的部署方式有哪些?

Apache Flink 支持多种集群部署方式,以适应不同的运行环境和应用需求。主要的部署方式包括: 1. 独立部署(Standalone) 特点:Flink 自带的简单集群模式,不依赖于外部的集群管理系统。适用场景:适用于小规模集群、测试或者学习环境。配置:需要手动配置 JobManager 和 Tas…

【p6】根据语法树求短语,直接短语和句柄

目录 步骤把根节点全部圈出来短语直接短语句柄 步骤 把根节点全部圈出来 意思是有孩子结点的结点圈出来 短语 从最后一层往上看&#xff0c;把根的叶子进行组合&#xff0c;在同一层上从左到右看&#xff0c;注意&#xff0c;这里说的叶子&#xff0c;是指每一个分支的最下…

【QT教程】QT6实时系统编程 QT实时系统

QT6实时系统编程 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看 免费…