Redis hash表源码解析
1、 整体数据结构
链式hash解决hash冲突、采用渐进式hash来完成扩容过程。
/** 哈希表节点*/
typedef struct dictEntry {// 键void *key;// 值union {void *val;uint64_t u64;int64_t s64;} v;// 指向下个哈希表节点,形成链表struct dictEntry *next;} dictEntry;/** 字典类型特定函数*/
typedef struct dictType {// 计算哈希值的函数unsigned int (*hashFunction)(const void *key);// 复制键的函数void *(*keyDup)(void *privdata, const void *key);// 复制值的函数void *(*valDup)(void *privdata, const void *obj);// 对比键的函数int (*keyCompare)(void *privdata, const void *key1, const void *key2);// 销毁键的函数void (*keyDestructor)(void *privdata, void *key);// 销毁值的函数void (*valDestructor)(void *privdata, void *obj);} dictType;/* This is our hash table structure. Every dictionary has two of this as we* implement incremental rehashing, for the old to the new table. */
/** 哈希表** 每个字典都使用两个哈希表,从而实现渐进式 rehash 。*/
typedef struct dictht {// 哈希表数组dictEntry **table;// 哈希表大小unsigned long size;// 哈希表大小掩码,用于计算索引值// 总是等于 size - 1unsigned long sizemask;// 该哈希表已有节点的数量unsigned long used;} dictht;/** 字典*/
typedef struct dict {// 类型特定函数dictType *type;// 私有数据void *privdata;// 哈希表:两个Hash表,里面存储的是键值对,交替使用,用于rehash操作dictht ht[2];// rehash 索引// 当 rehash 不在进行时,值为 -1int rehashidx; /* rehashing not in progress if rehashidx == -1 */// 目前正在运行的安全迭代器的数量int iterators; /* number of iterators currently running */} dict;
2、扩容函数
static int _dictExpandIfNeeded(dict *d)
触发该函数的三个操作:添加修改场景下会触发。
dictAdd:用来往 Hash 表中添加一个键值对。
dictRelace:用来往 Hash 表中添加一个键值对,或者键值对存在时,修改键值对。
dictAddorFind:间接调用 dictAddRaw。_dictExpandIfNeeded->_dictKeyIndex->dictAddRaw
static int _dictExpandIfNeeded(dict *d) 里面的代码片段截取//扩容两个条件// 1)字典已使用节点数趋近用完,并且没有开启aof和rdb两个操作// 2)Hash表承载的元素个数已是当前大小的5倍if (d->ht[0].used >= d->ht[0].size &&(dict_can_resize ||d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)){// 新哈希表的大小至少是目前已使用节点数的两倍// T = O(N)return dictExpand(d, d->ht[0].used*2);}//以下代码可以发现,但凡开启rdb和aof两个操作,必定禁止rehash。
void dictEnableResize(void) {dict_can_resize = 1;
}void dictDisableResize(void) {dict_can_resize = 0;
}void updateDictResizePolicy(void) {if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)dictEnableResize();elsedictDisableResize();
}
3、rehash(渐进式hash过程)
rehash是由ht[0]和ht[1]两个hashTable组成的,操作的粒度是hash表里面的bucket组成的。
1、ht[0]供正常使用,ht[1]供rehash使用,从ht[0]迁移到ht[1]会从新计算hash位置,操作粒度是bucket级别。
2、当迁移完成后,ht[1]表供正常使用,ht[0]表指向空。如果还需要迁移,那么ht[0]表就会变成rehash那张表,ht[1]就会供正常使用。
3、迁移是一个渐进式过程,通过记录rehashidx来标记上一次执行的位置,这样的好处是可以避免一次性消耗巨大导致redis过慢,因为redis是一个单线程过程。
/* This function performs just a step of rehashing, and only if there are* no safe iterators bound to our hash table. When we have iterators in the* middle of a rehashing we can't mess with the two hash tables otherwise* some element can be missed or duplicated.** 在字典不存在安全迭代器的情况下,对字典进行单步 rehash 。** 字典有安全迭代器的情况下不能进行 rehash ,* 因为两种不同的迭代和修改操作可能会弄乱字典。** This function is called by common lookup or update operations in the* dictionary so that the hash table automatically migrates from H1 to H2* while it is actively used. ** 这个函数被多个通用的查找、更新操作调用,* 它可以让字典在被使用的同时进行 rehash 。** T = O(1)*/
static void _dictRehashStep(dict *d) {if (d->iterators == 0) dictRehash(d,1);
}/* Performs N steps of incremental rehashing. Returns 1 if there are still* keys to move from the old to the new hash table, otherwise 0 is returned.** 执行 N 步渐进式 rehash 。** 返回 1 表示仍有键需要从 0 号哈希表移动到 1 号哈希表,* 返回 0 则表示所有键都已经迁移完毕。** Note that a rehashing step consists in moving a bucket (that may have more* than one key as we use chaining) from the old to the new hash table.** 注意,每步 rehash 都是以一个哈希表索引(桶)作为单位的,* 一个桶里可能会有多个节点,* 被 rehash 的桶里的所有节点都会被移动到新哈希表。** T = O(N)*/
int dictRehash(dict *d, int n) {// 只可以在 rehash 进行中时执行if (!dictIsRehashing(d)) return 0;// 进行 N 步迁移// T = O(N)while(n--) {dictEntry *de, *nextde;/* Check if we already rehashed the whole table... */// 如果 0 号哈希表为空,那么表示 rehash 执行完毕// T = O(1)if (d->ht[0].used == 0) {// 释放 0 号哈希表zfree(d->ht[0].table);// 将原来的 1 号哈希表设置为新的 0 号哈希表d->ht[0] = d->ht[1];// 重置旧的 1 号哈希表_dictReset(&d->ht[1]);// 关闭 rehash 标识d->rehashidx = -1;// 返回 0 ,向调用者表示 rehash 已经完成return 0;}/* Note that rehashidx can't overflow as we are sure there are more* elements because ht[0].used != 0 */// 确保 rehashidx 没有越界assert(d->ht[0].size > (unsigned)d->rehashidx);// 略过数组中为空的索引,找到下一个非空索引while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;// 指向该索引的链表表头节点de = d->ht[0].table[d->rehashidx];/* Move all the keys in this bucket from the old to the new hash HT */// 将链表中的所有节点迁移到新哈希表// T = O(1)while(de) {unsigned int h;// 保存下个节点的指针nextde = de->next;/* Get the index in the new hash table */// 计算新哈希表的哈希值,以及节点插入的索引位置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;// 更新 rehash 索引d->rehashidx++;}return 1;
}
相关文章:

Redis hash表源码解析
1、 整体数据结构 链式hash解决hash冲突、采用渐进式hash来完成扩容过程。 /** 哈希表节点*/ typedef struct dictEntry {// 键void *key;// 值union {void *val;uint64_t u64;int64_t s64;} v;// 指向下个哈希表节点,形成链表struct dictEntry *next;} dictEntry;…...

dll动态链接库【C#】
1说明: 在C#中,dll是添加 【类库】生成的。 2添加C#的dll: (1)在VS中新建一个Windows应用程序项目,并命名为TransferDll。 (2)打开Windows窗体设计器,从工具箱中为窗体…...
Linux 系统设置cpu频率
source_code: https://github.com/emagii/cpufrequtils cpufreq-set - A small tool which allows to modify cpufreq settings.(修改内存频率的工具) cpufreq-set allows you to modify cpufreq settings without having to type e.g. “/sys/devices…...

git基本概念
一、版本控制概念 1.1 什么是版本控制 1.1.1 手动管理文件版本 1.1.2 版本控制软件 概念:版本控制软件是一个用来记录文件发生的变化,以便将来查阅特定版本修订情况的系统,有时也叫“版本控制系统”。通俗的理解就是把手工管理文件版本的方…...
多个HTML属性
在HTML中,属性用于提供有关HTML元素的附加信息。在这篇文章中你将学习多个HTML属性,它们可以增强网站的视觉吸引力。 接下来开始吧!🚀 Accept 属性 您可以将accept属性与<input>元素(仅用于文件类型ÿ…...

基于运算放大器的电压采集电路
一、运算放大器 运放推导的两个重要概念:虚短、虚断。 1、差分放大器 以差分放大器为例进行推导分析。 虚断–运放的"-“端、”“端的引脚电流接近为0; 根据基尔霍夫电流定律可知:iR1iRF,iR2iR3; iR1(Ui1-(V-…...

数字图像处理(实践篇) 十六 基于分水岭算法的图像分割
目录 一 分水岭算法 二 利用OpenCV实现分水岭算法的过程 三 实践 一 分水岭算法 基于任何灰度图像都可以视为地形表面,其中高强度表示山峰和山丘,而低强度表示山谷。首先,开始用不同颜色的水(标签)填充每个孤立的山…...
快速学习PyQt5的高级自定义控件
Pyqt5相关文章: 快速掌握Pyqt5的三种主窗口 快速掌握Pyqt5的2种弹簧 快速掌握Pyqt5的5种布局 快速弄懂Pyqt5的5种项目视图(Item View) 快速弄懂Pyqt5的4种项目部件(Item Widget) 快速掌握Pyqt5的6种按钮 快速掌握Pyqt5的10种容器&…...

Python中读写(解析)JSON文件的深入探究
目录 一、引言 二、如何读取JSON文件 三、如何写入JSON文件 四、如何解析JSON字符串 五、错误处理和异常处理 六、使用第三方库提高效率 七、总结 一、引言 在Python中,我们经常使用JSON(JavaScript Object Notation)格式来存储和传输…...
我获取股票和期货数据的常用函数
记录一下获取数据所使用的函数,以防止遗忘和方便查找。 # 获取掘金的数据 # 需要打开并登陆掘金终端 def get_data_juejin(symbol"bu2112",start"2021-8-1",end"2021-8-30 23:00:00",frequency"1800s",fields"eob,sy…...
高并发场景下的httpClient使用优化技巧
1. 背景 我们有个业务,会调用其他部门提供的一个基于http的服务,日调用量在千万级别。使用了httpclient来完成业务。之前因为qps上不去,就看了一下业务代码,并做了一些优化,记录在这里。 先对比前后:优化…...
用php上传图片到阿里云oss
如果你想自动创建目录并将文件上传到新的目录下,你可以使用阿里云 OSS 的 createObject 方法来实现。下面是更新后的示例代码: php <?php require_once __DIR__ . /vendor/autoload.php; // 引入 SDKuse OSS\OssClient; use OSS\Core\OssException;…...
服务器适合哪些使用场景_Maizyun
服务器适合哪些使用场景 在当今的数字化时代,服务器作为互联网基础设施的核心组件,被广泛应用于各种场景。本文将探讨服务器适合哪些使用场景。 一、Web服务器 Web服务器是服务器中最常见的一种,用于托管和运行网站。它负责处理来自客户端…...

发布“最强”AI大模型,股价大涨,吊打GPT4的谷歌股票值得投资吗?
来源:猛兽财经 作者:猛兽财经 谷歌在AI领域的最新进展,引发投资者关注 在谷歌-C(GOOGL)谷歌-A(GOOG)昨日发布了最新的AI大模型Gemini后,其股价就出现了大幅上涨,更是引发了投资者的密切关注&a…...

年度工作总结怎么写?掌握这些年终总结万能公式,让你的报告出彩无比!
光阴似箭,日月如梭,时间总是不疾不徐地向前奔去,转眼就来到了2023年的最后一个月,12月一到,上班族和打工人又要开始忙活工作总结的事情~ 工作总结,不仅是一年工作的回顾,更是未来规划的起点。你…...
常用Nmap脚本
端口扫描类脚本 Nmap是一款非常流行的端口扫描工具,它可以帮助渗透测试工程师识别目标网络上开放的端口,并提供有关这些端口的详细信息。Nmap还提供了一系列基于脚本的功能,这些脚本可以扩展Nmap的功能,使其能够更深入地探测目标网…...

在pom.xml中添加maven依赖,但是类里面import导入的时候报错
问题: Error:(27, 8) java: 类TestKuDo是公共的, 应在名为 TestKuDo.java 的文件中声明 Error:(7, 23) java: 程序包org.apache.kudu不存在 Error:(8, 23) java: 程序包org.apache.kudu不存在 Error:(9, 23) java: 程序包org.apache.kudu不存在 Error:(10, 30) jav…...
【NEON】学习资料汇总
一、资料链接 Guide : http://www.heenes.de/ro/material/arm/DEN0018A_neon_programmers_guide_en.pdf csdn博文1,基础案例: https://blog.csdn.net/kakasxin/article/details/103912832? csdn博文2,内部函数: ht…...
java中ReentrantLock的实现原理是什么?
ReentrantLock 的实现原理主要涉及到两个关键概念:同步器(Sync)和 AQS(AbstractQueuedSynchronizer,抽象队列同步器)。 ReentrantLock 使用 AQS 来实现可重入锁的机制。AQS 是 Java 并发包中的一个抽象基类…...
C语言精选——选择题Day40
第一题 1. int a[10] {2,3,5}, 请问a[3]及a[3]之后的数值是() A:不确定的数据 B:5 C:0 D:0xf f f f f f f f 答案及解析 C 数组的不完全初始化,会自动把没初始化的部分初始化为0; 第…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...