当前位置: 首页 > 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…...

面试题杂记

1.问&#xff1a;react的Fabric实现原理答&#xff1a;实际上就是虚拟dom那一套东西&#xff0c;只不过换了个名词2.问&#xff1a;react的fiber架构实现原理答&#xff1a;在react15及以前的协调过程是基于栈&#xff08;stack-based&#xff09;的&#xff0c;缺点是一个组件…...

AI核心概念解析:Agent、Prompt、Skill 及生态关系

&#x1f310; AI核心概念解析&#xff1a;Agent、Prompt、Skill 及生态关系 一、关键名词正确定义与原理 1. Agent&#xff08;智能体&#xff09; 指具备感知—决策—行动闭环能力的自主软件实体。它不是单个模型&#xff0c;而是一个系统架构&#xff1a;接收输入&#x…...

Qt消息框(QMessageBox)的全面使用指南

1.1 预定义消息框类型Qt提供6种标准消息类型&#xff0c;通过静态方法快速调用&#xff1a;类型调用方法适用场景消息提示框QMessageBox::information()普通信息展示警告提示框QMessageBox::warning()操作风险警示错误提示框QMessageBox::critical()严重错误警示确认选择框QMes…...

Symfony Monolog Bundle与现代日志系统:Sentry、Elasticsearch、Slack集成终极指南

Symfony Monolog Bundle与现代日志系统&#xff1a;Sentry、Elasticsearch、Slack集成终极指南 【免费下载链接】monolog-bundle Symfony Monolog Bundle 项目地址: https://gitcode.com/gh_mirrors/mo/monolog-bundle Symfony Monolog Bundle是Symfony框架中功能强大的…...

Vivado Linux版安装空间不足?手把手教你如何优化磁盘空间分配

Vivado Linux版安装空间优化实战指南&#xff1a;从130G到80G的瘦身方案 当你在Linux系统上第一次看到Vivado安装程序提示需要130GB以上的磁盘空间时&#xff0c;那种震惊感我至今记忆犹新。作为一名长期在ThinkPad X1 Carbon上工作的FPGA开发者&#xff0c;我深刻理解空间受限…...

ESC固件底层开发:寄存器级驱动与无传感器换相实现

1. ESC固件底层技术解析&#xff1a;电子调速器固件架构与驱动实现电子调速器&#xff08;Electronic Speed Controller, ESC&#xff09;是无人机、电动航模、机器人驱动系统中的核心执行单元&#xff0c;其本质是一个高动态响应的三相逆变器控制器。ESC固件并非简单的PWM输出…...

量子态可视化太难?用C++ + ImGUI实时渲染Bloch球+概率幅热力图(含跨平台编译脚本)

第一章&#xff1a;量子态可视化太难&#xff1f;用C ImGUI实时渲染Bloch球概率幅热力图&#xff08;含跨平台编译脚本&#xff09;量子计算教学与算法调试中&#xff0c;单量子比特态的几何表示——Bloch球——是理解叠加、相位与测量的核心工具&#xff1b;而复数概率幅的模…...

94吨黄金“上链搬家”,手续费仅0.0016%!黄金RWA正在改写跨境资产流动

传统金融数百万美元的物流成本vs区块链毫厘之间的链上费用&#xff0c;资产数字化的未来已来。近日&#xff0c;Tether首席执行官Paolo Ardoino在X平台发文称&#xff1a;过去6个月内&#xff0c;共有价值约94吨黄金的代币化黄金XAUT在链上完成转移&#xff0c;合计手续费仅约0…...

AI 模型推理中的延迟分析与测试

AI 模型推理中的延迟分析与测试 在人工智能技术快速发展的今天&#xff0c;AI 模型的推理性能成为影响实际应用效果的关键因素之一。无论是智能语音助手、自动驾驶&#xff0c;还是实时推荐系统&#xff0c;延迟的高低直接决定了用户体验的好坏。对 AI 模型推理的延迟进行分析…...

5分钟掌握LibreHardwareMonitor:完全免费的硬件监控终极方案

5分钟掌握LibreHardwareMonitor&#xff1a;完全免费的硬件监控终极方案 【免费下载链接】LibreHardwareMonitor Libre Hardware Monitor is free software that can monitor the temperature sensors, fan speeds, voltages, load and clock speeds of your computer. 项目地…...