浅析Redis④:字典dict实现

什么是dict?

在 Redis 中,dict 是指哈希表(hash table)的一种实现,用于存储键值对数据。dict 是 Redis 中非常常用的数据结构之一,用于实现 Redis 的键空间。

在 Redis 源码中,dict 是一个通用的、高性能的哈希表实现,它采用开放寻址法(open addressing)作为冲突解决方案,并且具有良好的性能特征。

在 Redis 的源码中,dict 被用于实现 Redis 中的数据库、哈希键(hash key)等数据结构。通过 dict 这一数据结构,Redis 能够高效地实现键值对的存储和检索,保证了 Redis 的高性能和快速响应。

dict 在 Redis 中扮演着非常重要的角色,是支撑 Redis 数据存储和操作的基础之一。

核心特性

  • 开放寻址法:Redis 中的字典采用了开放寻址法作为冲突解决方案。在发生哈希冲突时,它会通过线性探测(linear probing)的方式来寻找下一个可用的位置。
  • 渐进式 rehashing:Redis 的字典实现中采用了渐进rehashing 策略,这意味着在进行扩容或缩小操作时,不会一次性地重新分配所有元素,而是逐步迁移键值对,以降低对服务的影响。
  • 哈希表的大小:Redis 中的字典会根据当前包含的元素数量动态调整哈希表的大小,以保证较低的负载因子,从而保持较好的性能。
  • 链表:在哈希表的每个槽位上,可以形成一个链表,用于处理哈希冲突时的多个元素。当链表长度过长时,Redis 会将链表转换为更高效的哈希表。

总的来说,Redis 中的字典通过哈希表实现,结合了开放寻址法、渐进式 rehashing 等策略,以及动态调整大小等特性,为 Redis 提供了高效的键值对存储和检索能力。这些特性使得 Redis 的字典在处理大量数据时依然能够保持良好的性能。

聚焦问题

1、dict的数据结构长什么样子,对比Java的HashMap

2、dict如何插入数据,并且如何解决hash冲突的

3、dict的rehash策略

dict的数据结构

typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;
} dictEntry;
typedef struct dictht {dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;
} dictht;

img

dict的本质是使用一个数组来存储key-value键值对,数组中的每个元素都是一个指向dictEntry结构的指针,而每一个dictEntry则保存了key-value键值对,是否听起来似曾相识,与Java中HashMap的结构非常相似

img

之所以说非常相似,而不是一模一样,是因为关于hash寻址冲突后,对同一个数组位置上元素的存储结构,并不完全一致,HashMap对于同一个数组位置的元素初始会使用链表的形式组装,而链表的长度达到一定阈值后,则转为红黑树的形式组装。

而dict并没有这种处理方式,仅会使用链表组织元素,这样的好处显而易见,链表的实现难度一定是比红黑树简单,也更易于调试,但是对与hash寻址算法的要求也更高,需要尽可能的保证少量的hash冲突,即寻址计算尽可能分散,否则链表长度过长,会影响取值的效率。

渐进式rehash

Redis 使用渐进式 rehash 来实现哈希表的扩容和缩容。渐进式 rehash 是一种在 Redis 服务不中断的情况下进行哈希表 rehash 的方法,它将 rehash 操作分解成多个小步骤,每次执行一小部分 rehash 操作,以避免阻塞服务。

渐进式 rehash 的主要步骤如下:

  1. 创建新哈希表: 当需要进行哈希表扩容或缩容时,Redis 首先会创建一个新的哈希表,其大小为原哈希表的两倍或更小。
  2. 迁移数据: 然后,Redis 会将原哈希表中的数据逐步迁移到新哈希表中。每次迁移一小部分数据,而不是一次性全部迁移。这样做的好处是,在迁移的过程中,Redis 仍然可以接受读取请求,并且只有在写入数据时才会阻塞。
  3. 逐步完成: 当所有的数据都迁移完成后,Redis 将原哈希表替换为新哈希表。这个过程是逐步进行的,不会中断服务。

渐进式 rehash 的优点包括:

  • 减少阻塞时间: 由于将 rehash 操作分解成多个步骤执行,因此可以大大减少阻塞时间,提高系统的稳定性和可用性。
  • 逐步进行: rehash 操作是逐步进行的,可以平滑地将数据从旧哈希表迁移到新哈希表,不会对系统造成过大的负担。
  • 保证读写服务: 在 rehash 过程中,Redis 仍然可以提供读取服务,只有在写入数据时才会阻塞。

需要注意的是,虽然渐进式 rehash 可以减少阻塞时间,但在 rehash 过程中,仍然会占用一定的系统资源,可能会对性能产生一定的影响。因此,在进行 rehash 操作时,需要谨慎安排时间,并在低负载时执行。

下面我们来一起看一下,具体实现逻辑:

/* This function handles 'background' operations we are required to do* incrementally in Redis databases, such as active key expiring, resizing,* rehashing. */
void databasesCron(void) {if (server.active_expire_enabled && server.masterhost == NULL)activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {static unsigned int resize_db = 0;static unsigned int rehash_db = 0;int dbs_per_call = CRON_DBS_PER_CALL;int j;/* Don't test more DBs than we have. */if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;/* Resize */for (j = 0; j < dbs_per_call; j++) {tryResizeHashTables(resize_db % server.dbnum);resize_db++;}/* Rehash */if (server.activerehashing) {for (j = 0; j < dbs_per_call; j++) {// rehash执行int work_done = incrementallyRehash(rehash_db % server.dbnum);rehash_db++;if (work_done) {/* If the function did some work, stop here, we'll do* more at the next cron loop. */break;}}}}
}
int incrementallyRehash(int dbid) {/* Keys dictionary */if (dictIsRehashing(server.db[dbid].dict)) {dictRehashMilliseconds(server.db[dbid].dict,1);return 1; /* already used our millisecond for this loop... */}/* Expires */if (dictIsRehashing(server.db[dbid].expires)) {dictRehashMilliseconds(server.db[dbid].expires,1);return 1; /* already used our millisecond for this loop... */}return 0;
}
/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
int dictRehashMilliseconds(dict *d, int ms) {long long start = timeInMilliseconds();int rehashes = 0;// 每次执行100次,这就是渐进while(dictRehash(d,100)) {rehashes += 100;if (timeInMilliseconds()-start > ms) break;}return rehashes;
}
int dictRehash(dict *d, int n) {int empty_visits = n*10; /* Max number of empty buckets to visit. */if (!dictIsRehashing(d)) return 0;// 注意此处的边界条件,每次传入100,也就是只会执行100次循环while(n-- && d->ht[0].used != 0) {dictEntry *de, *nextde;/* Note that rehashidx can't overflow as we are sure there are more* elements because ht[0].used != 0 */assert(d->ht[0].size > (unsigned long)d->rehashidx);while(d->ht[0].table[d->rehashidx] == NULL) {d->rehashidx++;if (--empty_visits == 0) return 1;}de = d->ht[0].table[d->rehashidx];/* Move all the keys in this bucket from the old to the new hash HT */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;d->rehashidx++;}/* Check if we already rehashed the whole table... */if (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;}/* More to rehash... */return 1;
}

rehash开始

img

rehash结束

img

Redis的dict与Java中的HashMap,实现上有什么区别

Redis 的 dict 是在 C 语言中实现的字典结构,而 Java 中的 HashMap 是在 Java 编程语言中实现的哈希表结构。它们之间有以下几个主要区别:

  • 语言差异:dict 是 Redis 自行实现的数据结构,使用 C 语言编写。而 HashMap 是 Java 标准库中提供的类,使用 Java 编写。
  • 内存管理:Redis 的 dict 使用了手动内存管理,需要手动分配和释放内存空间。而 Java 的 HashMap 使用垃圾回收机制自动管理内存,无需手动处理。
  • 线程安全性:Redis 的 dict 不是线程安全的,如果多个线程同时对同一个 dict 进行读写操作,可能会导致数据不一致或者程序崩溃。而 Java 的 HashMap 是非线程安全的,但可以通过使用 ConcurrentHashMap 等线程安全的 Map 实现来解决并发访问的问题。
  • 扩容机制:Redis 的 dict 在进行扩容时使用了渐进式 rehash 算法,将扩容操作分摊到多个操作中,避免了大规模数据迁移带来的性能问题。而 Java 的 HashMap 在扩容时需要重新计算哈希值,并将元素重新分布到新的桶位中。
  • 功能差异:Redis 的 dict 是为了满足 Redis 数据库的需要而设计的,支持一些特定的功能,如过期时间等。Java 的 HashMap 是通用的哈希表实现,提供了丰富的方法和功能,适用于多种应用场景。

总体而言,Redis 的 dict 和 Java 的 HashMap 在实现上有一些差异,主要是因为它们所运行的环境和使用的编程语言不同。它们在性能、线程安全性、扩容策略等方面都有各自的特点和优化。

Redis dict的rehash和Java HashMap的rehash,有什么不同

Redis 的 dict 和 Java 的 HashMap 在 rehash(重新哈希)操作上有一些区别:

  • 触发条件:Redis 的 dict 在进行 rehash 操作时,是在字典的负载因子(load factor)超过阈值时触发的,即元素数量超过容量的一定比例。而 Java 的 HashMap 在进行 rehash 操作时,是在桶位(buckets)的使用情况超过阈值时触发的,即某个桶位中元素的数量超过链表长度的阈值(8)。
  • 扩容方式:Redis 的 dict 采用了渐进式 rehash 算法,将扩容操作分摊到多个操作中,避免了大规模数据迁移带来的性能问题。具体而言,dict 在进行 rehash 时会逐步将 ht[0] 中的元素迁移到 ht[1] 中,直到完成整个 rehash 过程。而 Java 的 HashMap 使用的是一次性扩容的方式,在 rehash 时会创建一个更大的桶位数组,并将所有元素重新计算哈希值后放入新的桶位中。
  • 并发处理:Redis 的 dict 在 rehash 过程中,仅支持单线程执行,不支持并发访问。这意味着在 rehash 过程中,不能同时进行读取和写入操作,否则可能导致数据不一致。而 Java 的 HashMap 在 rehash 过程中,不会影响并发访问的正常进行。通过使用读写锁或者分段锁等机制,可以在不阻塞其他线程的情况下进行并发读取和写入操作。

Redis 的 dict 和 Java 的 HashMap 在 rehash 操作上有一些差异。Redis 的 dict 使用了渐进式 rehash 算法,避免了大规模数据迁移带来的性能问题,但不支持并发访问。而 Java 的 HashMap 在 rehash 过程中可以进行并发访问,但需要一次性扩容,并重新计算哈希值。这些差异是由于它们所处的环境和应用场景的不同导致的。

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

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

相关文章

IO流-字节缓冲流

简介 缓冲流就是对原始流进行包装&#xff0c;以提高原始数据流读写数据的性能 缓冲流继承体系 缓冲流的作用 构造器API 代码示例 try(// 创建字节输入流和输出流InputStream is new FileInputStream("test.txt");OutputStream os new FileOutputStream("test…

开源项目|使用go语言搭建高效的环信 IM Rest接口(附源码)

项目背景 环信 Server SDK 是对环信 IM REST API 的封装&#xff0c; 可以节省服务器端开发者对接环信 API 的时间&#xff0c;只需要配置自己的 App Key 相关信息即可使用。 环信目前提供java和PHP版本的Server SDK&#xff0c;此项目使用go语言对环信 IM REST API 进行封装…

在比特币中,1 sat 是多少美元?

普通人绝对想不到&#xff0c;比特币能在2024年达到这个价值&#xff0c;早知道的话&#xff0c;我当初就是破釜沉舟也得买一个啊。 而在4月19号&#xff0c;也将迎来比特币再次减半。减半并不是说玩家手中的比特币要被突然减去一半&#xff0c;而是在后续的挖矿过程中&#xf…

【Unity】游戏场景添加后处理特效PostProcessing

添加后处理特效PostProcessing 添加雾效果后处理何为后处理&#xff1f;添加后处理特效 添加雾效果 依次点击Window -> Rendering -> Lighting添加Lighting面板。 点击Lighting里面的Environment&#xff0c;找到Other Setting 将Fog选项勾选 更改下方的颜色 调整雾的浓…

移动端web适配方案

以下是移动端适配的多个方案&#xff0c;也可以说说你是怎么做的。 正文 自适应&#xff1a;根据不同的设备屏幕大小来自动调整尺寸、大小 响应式&#xff1a;会随着屏幕的实时变动而自动调整&#xff0c;是一种更强的自适应 为什么要做移动端适配&#xff1f; 目前市面上…

Linux内核与基础命令学习总结

Linux操作系统 Linux操作系统博大精深&#xff0c;其中对线程&#xff0c;IO&#xff0c;文件系统等概念的实现都很有借鉴意义。 ​ 文件系统和VFS 文件系统的inode上面讲过了。VFS主要用于屏蔽底层的不同文件系统&#xff0c;比如接入网络中的nfs文件系统&#xff0c;亦或是w…

如何使用docker-compose安装数据可视化应用JSON Crack并实现远程访问

文章目录 1. 在Linux上使用Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 JSON Crack 是一款免费的开源数据可视化应用程序&#xff0c;能够将 JSON、YAML、XML、CSV 等数据格式可视化为交互…

SAP SD学习笔记08 - Pre-sales(售前)引合,見積的概念,数据流(完了规则和参照Status),Copy管理,VBKD表的明细

上一章讲了紧急发注&#xff0c;现金贩卖&#xff0c;贩卖传票&#xff0c;明细Category等知识。 SAP SD学习笔记07 - 紧急发注&#xff08;急单&#xff09;&#xff0c;现金贩卖&#xff0c;贩卖传票Type/ 明细Category 及其Customize-CSDN博客 - 本张继续讲SAP SD模块的流程…

青铜器RDM研发管理平台 upload 任意文件上传漏洞复现

0x01 产品简介 青铜器RDM研发管理平台是集成产品管理、研发部门管理、研发项目管理、研发多项目管理、研发资源管理、研发绩效管理、研发工程管理的集中平台。 0x02 漏洞概述 青铜器RDM研发管理平台 upload 接口存在任意文件上传漏洞,未经身份验证的远程攻击者可通过该漏洞…

【板栗糖GIS】如何给微软拼音输入法加上小鹤双拼

【板栗糖GIS】如何给微软拼音输入法加上小鹤双拼 用过在注册表里新建的方法&#xff0c;结果弄完没有出现小鹤双拼方案&#xff0c;想到了自己写reg表 目录 1. 新建一个txt文件 2. 把.txt的后缀名改成.reg&#xff0c;双击运行 3. 在设置中找到微软输入法-常规 1. 新建一个…

微信预约怎么做_体验便捷的服务

在这个快节奏的时代&#xff0c;时间显得格外珍贵。无论是工作还是生活&#xff0c;我们都渴望能够用最短的时间完成更多的事情。在这样的背景下&#xff0c;微信预约应运而生&#xff0c;成为我们追求高效、便捷生活的新宠。今天&#xff0c;就让我们一起探讨微信预约的魅力&a…

相机系列——透视投影:针孔相机模型

作者&#xff1a;木一 引言 上文我们提到&#xff0c;三维相机是对真实世界成像的模拟&#xff0c;为了让三维物体在计算机屏幕上呈现出来的图像符合人眼观察效果&#xff0c;通常采用透视投影方式模拟相机成像&#xff0c;为了简化计算&#xff0c;可以用针孔相机模型来描述…

麒麟服务器操作系统安装HTTP服务

往期好文&#xff1a;麒麟服务器操作系统安装TFTP服务 Hello&#xff0c;大家好啊&#xff01;今天我们将探讨如何在麒麟服务器操作系统上安装和配置HTTP服务&#xff0c;这是任何网络服务或应用的基础。无论你是想建立一个简单的网站&#xff0c;还是需要一个全功能的Web服务器…

CodeMirror使用: 编写一个在线编辑HTML、JS、CSS文件,网页的模板页面-初实现

前言&#xff1a;前几天编写一个UI模板控制的功能&#xff0c;根据上传的前端模板更换跳转入口主题页面&#xff1b;在编写的时候&#xff0c;突发奇想能不能在列表页面进行在线编辑刚刚上传的模板zip压缩包里的页面...于是经过学习研究有了这篇文章&#xff1b;当日记本一样记…

图灵《模仿游戏》论文学习

文章目录 1. 写在最前面2. 核心观点学习2.1 脑图观点记录2.2 经典观点记录 3. 感受4. 碎碎念5. 参考资料 1. 写在最前面 3 月看了一部以图灵为原型拍摄的人物传记类电影《模仿游戏》&#xff0c;里面反复提及到的论文《COMPUTING MACHINERY AND INTELLIGENCE》&#xff0c;引起…

计算机丢失VCRUNTIME140_1.dll处理办法

一、打开 下面连接地址&#xff0c;下载Visual Studio 2015, 2017, 2019, and 2022 https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?viewmsvc-170#visual-studio-2015-2017-2019-and-2022 二、下载系统对应的版本 32位系统下载X86 64位系统下载X…

win10 鼠标箭头自己乱动解决方案

我这里只说我碰到的然后我的解决方案&#xff0c;不一定对其他问题有效&#xff1b; 1. 首先拔掉鼠标线查看鼠标箭头是否仍然在乱动&#xff0c;如果是则非鼠标问题&#xff0c;如果不再乱动则是鼠标的问题&#xff1b;验证非鼠标问题&#xff1b; 2. 因为鼠标乱动跟鼠标无关…

【深度学习】AI修图——DragGAN原理解析

1、前言 上一篇&#xff0c;我们讲述了StyleGAN2。这一篇&#xff0c;我们就来讲一个把StyleGAN2作为基底架构的DragGAN。DragGAN的作用主要是对图片进行编辑&#xff0c;说厉害点&#xff0c;可能和AI修图差不多。这篇论文比较新&#xff0c;发表自2023年 原论文&#xff1a…

韩顺平 | 零基础快速学Python(16) 文件处理

文件 输入与输出 输入&#xff1a;数据从数据源(文件)到程序(内存)&#xff1b; 输出&#xff1a;数据从程序(内存)到数据源(文件)。 #mermaid-svg-06PG6JZq4jJMV1oH {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-sv…

数据库查询:查询入参类型和数据库字段类型不匹配导致的问题

问题&#xff1a;假设我们现在有这样的一张表 CREATE TABLE test_person (id int(20) NOT NULL COMMENT 主键,name varchar(20) DEFAULT NULL COMMENT 姓名,gender char(2) DEFAULT NULL COMMENT 性别,birthday date DEFAULT NULL COMMENT 生日,created_time timestamp NULL D…