Redis rehash 相关问题

news/2024/5/19 20:37:31

前言

本文主要介绍 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.mrgr.cn/p/56603855

相关文章

03_Redis

文章目录 Redis介绍安装及使用redis的核心配置数据结构常用命令stringlistsethashzset(sortedset) 内存淘汰策略Redis的Java客户端JedisRedisson Redis 介绍 Redis是一个NoSQL数据库。 NoSQL: not only SQL。表示非关系型数据库(不支持SQL标准语法)。 …

Mybatis逆向工程笔记小结

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Java全栈-专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 目录 1.前言 2.实现方案 2.1. mybatis-generator生成 2.1.1. 环境说明 2.1.2. 数…

算法学习:二分查找

🔥 引言 在现代计算机科学与软件工程的实践中,高效数据检索是众多应用程序的核心需求之一。二分查找算法,作为解决有序序列查询问题的高效策略,凭借其对数时间复杂度的优越性能,占据着算法领域里举足轻重的地位。本篇内…

【配置】Docker搭建JSON在线解析网站

一个python朋友需要,顺便做一下笔记 正常用菜鸟的就够了,点下面 JSON在线解析 云服务器打开端口8787 连接上docker运行 docker run -id --name jsonhero -p 8787:8787 -e SESSION_SECRETabc123 henryclw/jsonhero-webhttp://ip:8787访问 Github&…

合泰杯(HT32F52352)RTC的应用(计时)--->掉电不丢失VBAT(代码已经实现附带源码)

摘要 在HT32F52352合泰单片机开发中,rtc在网上还是挺少人应用的,找了很久没什么资料,现在我根据手册和官方的代码进行配置理解。 RTC在嵌入式单片机中是一个很重要的应用资源。 记录事件时间戳:RTC可以记录事件发生的精确时间&…

【高校科研前沿】中国科学院地理资源所钟帅副研究员研究组博士生朱屹东为一作在Top期刊发文:从潜力到利用:探索西藏风能资源开发的技术路径优化布局

01 文章简介 论文名称:From potential to utilization: Exploring the optimal layout with the technical path of wind resource development in Tibet(从潜力到利用:探索西藏风能资源开发的技术路径优化布局) 文章发表期刊:《…

【无标题】场外个股期权多少钱才能做?个人能做吗?

场外个股期权的交易门槛相对较高,主要面向符合特定条件的机构投资者。一般来说,法人或合伙企业等组织参与的,需要满足最近1年末净资产不低于5000万元人民币、金融资产不低于2000万元人民币的条件,并具备3年以上证券、基金、期货、…

(六)SQL系列练习题(下)#CDA学习打卡

目录 三. 查询信息 16)检索"1"课程分数小于60,按分数降序排列的学生信息​ 17)*按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩 18)*查询各科成绩最高分、最低分和平均分 19)*按各科成绩…

ORAN C平面优化

使用section扩展6的C平面优化 在时域和频域中,都可以使用section扩展6进行非连续PRB分配。Section扩展6有两个位掩码:symbolMask和rbgMask。使用symbolMask可以选择一个slot内任意的symbol子集。使用rbgMask可以选择startPrbc和(startPrbc …

c3 笔记7 css基本语法

相关内容:字体、段落、词间距、文字效果(对齐、上下标、阴影)、背景图、背景渐变、…… 单位pt与px的差别pt是印刷使用的字号单位,不管屏幕分辨率是多少,打印到纸上看起来都是相同的,lot的长度是0.01384英寸…

[综合应用]dns nfs httpd php mysql

第一步:搭建三台主机 主机名称 Ip地址 角色 503A 192.168.68.10 Mysql从 503B 192.168.68.11 Mysql从,nfs服务端,dns服务端 503Cmysql 192.168.68.12 MySQL主,web客户端 第二步:在503B上配置DNS 2.1 下载…

为什么感觉没有效果

以前在辅导小儿作业的时候,我会在常用的搜索引擎里去寻找答案,一般情况下都能解决问题。 但是最近一段时间,我发现,搜索引擎搜出来的结果还没有利用短视频搜出来的答案更全面,短视频软件不仅可以显示AI整理出来的答案…

新旅程,新起点——盈致集团搬迁公告

在这春风得意的美好时光里,我们带着满腔的热忱向各位好友宣布一个重要消息:盈致集团即将展开新的篇章,我们的办公地址将迁移至一个全新的地点。新的环境,新的开始,我们期待在这片充满潜力的土地上,继续书写…

第四章——操作系统基本原理(6)

基本概念,进程管理,存储管理,文件管理,设备管理,微内核操作系统第四章 操作系统基本原理 4.1 基本概念 计算机系统的层次结构:纯硬件->操作系统->软件/用户操作系统(Operating System,OS)是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的…

第二章——数据结构与算法基础(占比较高)

基本概念和三要素,算法,线性表,栈和队列,串、数组、矩阵和广义表,树和二叉树,图,查找,排序第二章 数据结构与算法基础(占比较高) 2.1 基本概念和三要素 数据结构在学什么? 如何用程序代码把现实世界的问题信息化 如何用计算机高效地处理这些信息从而创造价值,数据:…

第十三章——法律法规与标准化知识(2分)

知识产权,保护期限,知识产权人确定,侵权判定,标准的分类与标准的编号第十三章 法律法规与标准化知识(2分) 13.1 知识产权 知识产权又称为智慧财产权,是指人们通过自己的智力活动创造的成果和经营管理活动中的经验、知识而依法所享有的权利。传统的知识产权可分为“工业产…

第五章——计算机网络基础(浅浅的了解一下即可)

计算机网络的分类,七层网络体系结构,网络的标准,TCP/IP协议族,IP地址和IPv6简介,Internet服务第五章 计算机网络基础(浅浅的了解一下即可) 5.1 计算机网络的分类5.2 七层网络体系结构5.3 网络的标准 主要的国际标准化组织如下ISO —— 国际标准化组织 ANSI —— 美国国家…

家政保洁上门预约服务小程序源码系统 带完整的安装代码包以及搭建教程

随着社会的快速发展和人们生活节奏的加快,家政保洁服务已成为现代生活中不可或缺的一部分。为了满足广大用户的需求,罗峰给大家分享一款家政保洁上门预约服务小程序源码系统,该系统不仅提供完整的安装代码包,还附带详细的搭建教程…

毕业设计参考-PyQt5-YOLOv8-鱼头鱼尾鱼长测量程序,OpenCV、Modbus通信、YOLO目标检测综合应用

“PyQt5-YOLOv8-鱼头鱼尾鱼长测量程序”是一个特定的软件程序,用于通过图像处理和目标检测技术来测量鱼类的长度。 视频效果: 【毕业设计】基于yolo算法与传统机器视觉的鱼头鱼尾识别_哔哩哔哩_bilibili 这个程序结合了多种技术: 1. OpenCV…

关于keil中勾选微库Use MicroLIB调试printf时编译报错问题

报错内容: .\Objects\01_USART_Printf.axf: Error: L6218E: Undefined symbol __use_two_region_memory (referred from startup_gd32e23x.o). .\Objects\01_USART_Printf.axf: Error: L6218E: Undefined symbol __initial_sp (referred from entry2.o).问题描述 在keil中勾选…