当前位置: 首页 > news >正文

Redis源码学习:高性能Hash表的设计与实现

哈希表(Hash)是Redis数据库的数据类型之一,理解哈希表的实现对于掌握Redis非常重要。这篇文章,从哈希冲突和哈希扩展这两个角度,来一步步讲解Redis哈希表的工作原理。

什么是哈希表?

哈希表是一种通过哈希函数将键映射到值的数据结构。简单来说,就是通过一个计算公式(哈希函数)把一个键(比如一个名字)转换成一个数组的索引,数组中的每一个元素就是一个哈希桶(也叫bucket),然后在这个索引位置存储对应的值(比如电话号码)。这样我们就能以O(1)的时间复杂度通过键快速找到对应的值。

哈希冲突

哈希冲突是什么?

当键的数量超过数组的大小,必然会出现两个不同的键通过哈希函数映射到数组的同一个位置时,就发生了哈希冲突。举个例子,如果我们把“Tom”和“Jerry”这两个名字通过同一个公式转换成同一个数组位置,这时候就会有冲突。

如何解决哈希冲突?

Redis使用链式哈希来解决这个问题。链式哈希的意思是,每个bucket不再只存一个值,而是变成一个链表的头指针。如果有冲突,新来的键值对就插到链表头中。这样,同一个位置上可以存多个键值对。

Redis中的链式哈希

在Redis使用链式哈希解决哈希冲突,每个bucket指向一个链表,源码(位于dict.h文件)如下:

// 哈希表的定义
typedef struct dictht {// 哈希项数组,保存指向哈希项的指针dictEntry **table;// 哈希表的大小unsigned long size;// 哈希表小的掩码,总是等于 size -1 unsigned long sizemask;// 哈希项的数量,因为有哈希冲突的存在,used可能会比size大unsigned long used;
} dictht;// 哈希项的定义
typedef struct dictEntry {void *key;  // 键union {void *val;uint64_t u64;int64_t s64;double d;} v;  // 值struct dictEntry *next;  // 指向下一个条目的指针(用于链式哈希)
} dictEntry;

每个dictEntry结构包含一个键key,一个值v,以及一个指向下一个条目的指针next

联合体v是一个四选一的类型,包含了指向实际值的指针 val,还包含了无符号的 64 位整数、有符号的 64 位整数,以及 double 类的值。这是一种节省内存的设计。当值为整数或双精度浮点数时,由于其本身就是 64 位,就可以不用指针指向了,而是可以直接存在键值对的结构体中,这样就避免了再用一个指针,从而节省了内存空间。

插入键值对时,如果发生冲突,新条目将插入到链表的头部。采用头插法的好处就是性能高,不需要遍历链表了。

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) {// 进行rehash检查if (dictIsRehashing(d)) _dictRehashStep(d);unsigned long idx = dictHashKey(d, key) & ht->sizemask;dictEntry *entry = ht->table[idx];while (entry) {if (dictCompareKeys(d, key, entry->key)) {if (existing) *existing = entry;return NULL;}entry = entry->next;}entry = zmalloc(sizeof(*entry));entry->next = ht->table[idx];ht->table[idx] = entry;ht->used++;dictSetKey(d, entry, key);return entry;
}

哈希扩展

为什么需要哈希扩展?

随着哈希表中存储的键值对越来越多,哈希冲突变得越来越频繁,哈希表的效率会降低。为了保持高效的性能,需要在恰当的扩展哈希表,也就是增加哈希表的大小。这个过程称为哈希扩展或rehash。

渐进式rehash

Redis采用渐进式rehash策略,以避免扩展过程中阻塞数据库的正常操作。也就是说,Redis不会一次性把所有数据搬到新的哈希表中,而是分多次慢慢进行,每次只处理一个bucket数据。

rehash的实现

Redis在dict结构中,定义了两个哈希表,用于 rehash 时交替保存数据。

typedef struct dict {dictType *type;void *privdata;dictht ht[2]; //两个Hash表,交替使用,用于rehash操作long rehashidx; //Hash表是否在进行rehash的标识,-1表示没有进行rehashint16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;

每次rehash只处理一个bucket的链表,这样就不会长时间阻塞数据库的其他操作。调用dictRehash函数,传入的参数n始终为1,这样一来,每次迁移完一个bucket,哈希表就会执行正常的增删查请求操作,这就是在代码层面实现渐进式 rehash 的方法。

以下是相关的源码片段:

void _dictRehashStep(dict *d) {if (d->iterators == 0) dictRehash(d, 1);
}int dictRehash(dict *d, int n) {if (!dictIsRehashing(d)) return 0;while (n--) {dictEntry *de, *nextde;/* 如果ht[0]迁移完 */if (d->ht[0].used == 0) {/* 释放ht[0]的内存空间 */zfree(d->ht[0].table);/* 让ht[0]执行ht[1],来接收请求 */d->ht[0] = d->ht[1];/* 重置ht[1]的大小为0 */_dictReset(&d->ht[1]);/* 值改完1,表示rehash结束 */d->rehashidx = -1;/* 返回0,表示ht[0]中所有元素都迁移完成 */return 0;}/* 当前正在迁移的桶*/while (d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;/* 哈希表中的哈希项 */de = d->ht[0].table[d->rehashidx];while (de) {unsigned long h;/* 获得下一个哈希项 */nextde = de->next;/* 当前哈希项在ht[1]的位置 */h = dictHashKey(d, de->key) & d->ht[1].sizemask;/* 添加到ht[1]中 */de->next = d->ht[1].table[h];d->ht[1].table[h] = de;/* 减少ht[0]中的哈希项 */d->ht[0].used--;/* 增加ht[1]中的哈希项*/d->ht[1].used++;/* 指向下一个哈希项 */de = nextde;}/* 如果当前bucket中已经没有哈希项了,将该bucket置为NULL */d->ht[0].table[d->rehashidx] = NULL;/* 将rehash加1,下一次将迁移下一个bucket中的元素 */d->rehashidx++;}/* 返回1,表示ht[0]中仍然有元素没有迁移 */return 1;
}
什么时候触发rehash

Redis中在每次的增删查中都会判断是否需要扩容,在函数_dictExpandIfNeeded中检查负载因子(LoadFactor=used/size),只要满足以下任意一个条件就会触发哈希表扩容:

  • 哈希表的LoadFactor>=1,并且服务器没有执行SAVE或REWRITEAOF等子进程
  • 哈希表的LoadFactor>5
static int _dictExpandIfNeeded(dict *d)
{/* Incremental rehashing already in progress. Return. */if (dictIsRehashing(d)) return DICT_OK;/* 如果哈希表为空,则进行初始化为4. */if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);if (!dictTypeExpandAllowed(d))return DICT_OK;if ((dict_can_resize == DICT_RESIZE_ENABLE &&d->ht[0].used >= d->ht[0].size) ||(dict_can_resize != DICT_RESIZE_FORBID &&d->ht[0].used / d->ht[0].size > dict_force_resize_ratio)){/* 扩容大小为used + 1, 底层会对扩容的大小做判断,实际上找的是第一个大于等于used+1的2倍 */return dictExpand(d, d->ht[0].used + 1);}return DICT_OK;
}
rehash扩容大小

通过函数_dictExpand对哈希表进行扩容,每次扩容大小总是2的幂,看下内部的 _dictNextPower函数源码:

static unsigned long _dictNextPower(unsigned long size)
{/* 哈希表的初始大小 */unsigned long i = DICT_HT_INITIAL_SIZE;/* 如果要扩容的大小已经超过了最大值,则返回最大值加1*/if (size >= LONG_MAX) return LONG_MAX + 1LU;/* 要扩容的大小没有超过最大值 */while(1) {/* 从DICT_HT_INITIAL_SIZE(通常是4)开始,不断将i乘以2,直到 i 大于等于size */if (i >= size)return i;i *= 2;}
}

总结

通过链式哈希,Redis有效地解决了哈希冲突的问题;通过渐进式rehash,Redis确保了哈希表扩展时的高效性和稳定性。这些机制让Redis哈希表在处理大量数据时仍然保持高效。

参考资料

  • Redis Documentation
  • Redis GitHub Repository
  • Redis源码剖析与实战

相关文章:

Redis源码学习:高性能Hash表的设计与实现

哈希表&#xff08;Hash&#xff09;是Redis数据库的数据类型之一&#xff0c;理解哈希表的实现对于掌握Redis非常重要。这篇文章&#xff0c;从哈希冲突和哈希扩展这两个角度&#xff0c;来一步步讲解Redis哈希表的工作原理。 什么是哈希表&#xff1f; 哈希表是一种通过哈希…...

如何防范常见的数据库安全问题

随着数据量的增加和系统的复杂性提高,数据库可能面临多种安全威胁,包括未授权访问、数据泄露、注入攻击等。 1. 未授权访问 未授权访问是指,未经授权的用户对数据库的内容进行访问。这会导致数据泄露、数据篡改或其他安全事故。 针对未授权访问的防范措施如下。 (1)强化…...

[Day 19] 區塊鏈與人工智能的聯動應用:理論、技術與實踐

區塊鏈的數據透明性 區塊鏈技術作為一種分布式賬本技術&#xff0c;因其去中心化、不可篡改和高度透明的特性&#xff0c;已經在各行各業中得到了廣泛應用。在本文中&#xff0c;我們將深入探討區塊鏈的數據透明性&#xff0c;包括其原理、實現方法及相關代碼示例&#xff0c;…...

【Hadoop学习笔记】认识Hadoop

认识Hadoop 从网上找的课程做的笔记&#xff0c;有些图是自己理解画的&#xff0c;可能不正确&#xff0c;可以作为参考&#xff0c;有疑问的地方请直接指出&#xff0c;共同交流。 Hadoop是由Apache基金会开发的一个分布式系统基础架构&#xff0c;主要解决海量数据的存储和海…...

CISP-PTE综合靶机-WinServer2003

1.收集网站的地址和开放的端口,完成前期信息收集。10分 2.访问站点,找出站点的敏感文件,利用返回数据找到相关敏感信 息,完成网站结构的信息收集。10分 3.利用文件包含漏洞读取敏感文件,找出数据库连接凭证,利用此 凭证连接数据库。10分 4.网站后台提权:找出后台管理员登…...

sklearn之各类朴素贝叶斯原理

sklearn之贝叶斯原理 前言1 高斯朴素贝叶斯1.1 对连续变量的处理1.2 高斯朴素贝叶斯算法原理 2 多项式朴素贝叶斯2.1 二项分布和多项分布2.2 详细原理2.3 如何判断是否符合多项式贝叶斯 3 伯努利朴素贝叶斯4 类别贝叶斯4 补充朴素贝叶斯4.1 核心原理4.2 算法流程 前言 如果想看…...

年薪50w+的项目经理,手把手教你如何复盘

复盘是一种重要的学习和改进工具&#xff0c;对于项目经理来说&#xff0c;能帮助识别项目中的成功与失败&#xff0c;为未来的项目管理提供宝贵经验。 理论部分 定义目标。在开始复盘之前&#xff0c;明确复盘的目标是什么。是为了找出项目中的问题并提出解决方案&#xff0c…...

Web3新视野:Lumoz节点的潜力与收益解读

摘要&#xff1a;低估值、高回报、无条件退款80%...... Lumoz正通过其 zkVerifier 节点销售活动&#xff0c;引领一场ZK计算革命。 长期以来&#xff0c;加密市场以其独特的波动性和增长潜力&#xff0c;持续吸引着全球投资者的目光。而历史数据表明&#xff0c;市场往往在一年…...

【shell脚本速成】mysql备份脚本

文章目录 案例需求脚本应用场景&#xff1a;解决问题脚本思路实现代码 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的每一刻…...

高考志愿填报,理科生如何分析选专业?

理科生选择专业的范围更大一些&#xff0c;相比文科说理工科的院校也更多&#xff0c;如何选择适合自己的专业&#xff0c;这是一个比较重要的课题&#xff0c;毕竟大学专业直接关系到职业&#xff0c;是一辈子的大事。 那么理科究竟如何选择专业呢&#xff1f;需要从什么地方…...

qt 简单实验 json格式的文件写入配置文件

1.概要 2.代码 //#include "mainwindow.h"#include <QApplication> #include <QFile> #include <QJsonDocument> #include <QJsonObject> //读取json数据的配置文件int main(int argc, char *argv[]) {QApplication a(argc, argv);QString…...

将WIN10的wifi上网分享给以太网接口

目录 打开网络设置设置属性点这里的设置将wlan主机的以太网接口IP设为自动获取 如果连接不成功&#xff0c;拔网线重连一次 打开网络设置 设置属性 点这里的设置 将wlan主机的以太网接口IP设为自动获取 如果连接不成功&#xff0c;拔网线重连一次...

在 iPhone 上恢复已删除联系人的 5 种简便方法

想象一下&#xff1a;您正在 iPhone 上滚动并搜索要拨打的联系人&#xff0c;但却找不到任何结果。然后您想起昨晚您试图删除一个名字相似的联系人&#xff0c;但不知何故删除了错误的联系人。或者您的孩子错误地删除了一些联系人。这些情况足以让您感到迷茫。但别担心&#xf…...

小白指南:前端使用javascript如何判断集合是不是空集合?

背景 最近在开发一个Web应用时&#xff0c;我遇到了一个关于集合处理的问题。具体来说&#xff0c;我需要判断一个集合是否为空。集合可以是数组、对象、Map或Set等不同的数据结构。就简单的整理了一下如何在JavaScript中有效地判断一个集合是否为空呢&#xff1f; 解决方案 …...

人力资源招聘社会校企类型招聘系统校园招聘小程序

校企社会人力资源招聘小程序&#xff1a;开启高效招聘新时代 &#x1f680;开篇&#xff1a;打破传统&#xff0c;开启招聘新篇章 在快速发展的现代社会&#xff0c;人力资源招聘已经成为企业和学校共同关注的重要议题。为了更高效、便捷地满足双方的招聘需求&#xff0c;一款…...

docker重要操作与直连方法

文章目录 前言一、nvidia-docker安装方法1、nvidia-docker安装2、重启动ssh 二、构建镜像1、构建镜像docker拉取构建本地镜像加载构建 2、容器转镜像3、镜像打包4、删除镜像 三、构建容器1、容器构建2、启动镜像3、删除容器 四、docker直连(ssh -p)1、docker更改密码2、物理机操…...

Windows环境利用 OpenCV 中 CascadeClassifier 分类器识别人眼 c++

Windows环境中配置OpenCV 关于在Windows环境中配置opencv的说明&#xff0c;具体可以参考&#xff1a;VS2022 配置OpenCV开发环境详细教程。 CascadeClassifier 分类器 CascadeClassifier 是 OpenCV 库中的一个类&#xff0c;它用于实现一种快速的物体检测算法&#xff0c;称…...

Golang | Leetcode Golang题解之第167题两数之和II-输入有序数组

题目&#xff1a; 题解&#xff1a; func twoSum(numbers []int, target int) []int {low, high : 0, len(numbers) - 1for low < high {sum : numbers[low] numbers[high]if sum target {return []int{low 1, high 1}} else if sum < target {low} else {high--}}r…...

【软件工程】【23.04】p2

关键字&#xff1a; 计算机软件定义、需求基本性质、创建系统类图所涉及的工作、RUP创建系统用况模型活动、软件生存周期模型、能力等级和成熟度等级区别联系&#xff1b; 模块结构图&#xff1a;深度宽度、扇入扇出、作用域、控制域&#xff1b; 程序流程图&#xff1a;语句…...

Java多线程编程与并发控制策略

Java多线程编程与并发控制策略 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我想和大家分享一下Java多线程编程与并发控制策略的相关知识&am…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

第25节 Node.js 断言测试

Node.js的assert模块主要用于编写程序的单元测试时使用&#xff0c;通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试&#xff0c;通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...