redis原理篇--Dict

Dict数据结构

一、Redis字典的核心组件

Redis字典由三部分构成:

  1. dictht(哈希表):存储桶数组与元数据
  2. dictEntry(哈希节点):存储键值对
  3. dict(字典主体):包含双哈希表(用于渐进式重哈希)和类型函数

二、哈希表结构 dictht(Dict HashTable)

typedef struct dictht {dictEntry **table;        // 桶数组指针unsigned long size;       // 哈希表总槽位数(桶大小)unsigned long sizemask;   // 掩码(size-1)unsigned long used;       // entry的数量
} dictht;
字段详解:
  1. dictEntry **table

    • 核心存储结构:指向指针数组(entry数组)的指针
    • 数组元素为dictEntry*类型(链表头指针)
  2. size

    • 哈希表的大小(总是2^n)
    • 通过_dictNextPower函数动态扩容(如4→8→16)
  3. sizemask

    • 核心优化设计:值为size-1
    • 作用:替代取模运算 index = hash & sizemask
  4. used

    • 当前有效节点数量(非空桶计数)
    • 缩容条件:used/size < 0.1

这里的 used即标识entry数量的字段可能比哈希表大小字段更大,因为可能计算出同样的hash值,所以value就放在同一个位置


三、哈希节点 dictEntry

typedef struct dictEntry {void *key;          // 键指针union {             // 联合值域(节约内存)void *val;      // 通用值指针uint64_t u64;   // 无符号64位整数int64_t s64;    // 有符号64位整数double d;       // 双精度浮点} v;struct dictEntry *next;  // 冲突链表指针
} dictEntry;
字段详解:
  1. void *key

    • 键的指针(支持任意数据类型)
    • 实际存储类型取决于Redis对象系统(SDS/INET等)
    • 通过dictType中的哈希函数计算索引
  2. 值联合体 v

    • 创新设计:共用内存空间(每次只用一种类型)
    • val:通用对象指针(如RedisObject)
    • u64/s64:直接存储整数(避免内存碎片)
    • d:直接存储浮点数(如ZSET的score)
    • 典型内存优化:8字节空间覆盖所有类型
  3. struct dictEntry *next

    • 冲突解决方案:单向链表
    • 头插法添加新节点(O(1)复杂度)
    • 链表长度>64触发树化(在rehash时转换)

Dict添加键值对过程

步骤1:计算哈希值与索引
// 示例:添加键值对 k1:v1
hash = dict->type->hashFunction(k1);  // 计算键k1的SipHash值
index = hash & dict->ht[0].sizemask;  // 图中sizemask=3

步骤2:解决哈希冲突
  1. 创建新entry
    entry = zmalloc(sizeof(dictEntry));
    entry->key = k1;
    entry->v.val = v1;
    
  2. 头插法链入
    entry->next = dict->ht[0].table[index]; 
    // 原table[1]指向k2节点,新entry指向k2
    

步骤3:更新哈希表状态
dict->ht[0].table[index] = entry;  // 桶指针指向新插入的entry
dict->ht[0].used++;                // used计数+1(used从1→2)

Dict数据结构中,默认使用Dict中的第一个哈希表作为数据的存储,图片中

  • ht[0]被初始化为一个dictht结构体(table数组大小为4,存储两个键值对k1:v1k2:v2)。
  • ht[1]null,所有字段(tablesize等)为空或0,表明当前未进行rehash。

Dict的扩容

每次扩容时都是扩容都是扩容到2**n,并且是第一个大于等于used+1的2**n
条件一:LoadFactor ≥ 1 且无后台进程运行
if (d->ht[0].used >= d->ht[0].size && dict_can_resize)  // 等效于"LoadFactor≥1"
  • 设计目的
    平衡内存效率和性能:当平均每个桶至少存储一个元素时(LoadFactor=1),冲突概率显著增加。此时扩容可降低冲突率,但需考虑系统负载。

  • 后台进程限制原因
    保护持久化操作

    1. 执行BGSAVEBGREWRITEAOF时,Redis使用写时复制(Copy-on-Write) 机制
    2. 若此时扩容(分配新哈希表),会触发大量内存页复制
    3. 可能耗尽内存或造成性能抖动(图示代码注释明确提到此保护逻辑)

条件二:LoadFactor > 5
// dict_force_resize_ratio 在源码中定义为5
if (d->ht[0].used/d->ht[0].size > dict_force_resize_ratio)  
  • 设计目的
    紧急避险机制
    当哈希冲突极度严重时(平均每个桶链长超过5),即使有后台进程运行也强制扩容,避免查询性能雪崩(链表遍历从O(1)退化为O(n))。

  • 临界值选择依据

    1. 经验值:桶链超过5个节点,查找效率明显下降(实测性能衰减曲线)
    2. 内存容忍上限:5倍空间浪费是可承受极限(例:16个桶存80个元素)

Dict的收缩

收缩大小为第一个大于等于used的2**n,此时的size远比used大,之所以是大于等于要保证实际

当删除元素后,Redis 会调用 htNeedsResize(dict) 检查是否满足收缩条件:

size = dictSlots(dict);   // 哈希表总槽位数
used = dictSize(dict);    // 已使用槽位数(键值对数量)
if (size > DICT_HT_INITIAL_SIZE && (used * 100 / size < HASHTABLE_MIN_FILL)) {  // 默认HASHTABLE_MIN_FILL=10return 1;  // 需要收缩
}

条件解读

  1. 哈希表大小超过初始值DICT_HT_INITIAL_SIZE,默认为4)
  2. 负载因子 < 10%(即已用槽位数不足总槽位的10%)
  3. 特殊保护:若 used < 4,强制按4计算,避免过度收缩

 收缩执行流程

1. 调用入口

删除元素后触发检查:

// t_hash.c
if (dictDelete(dict, field) == C_OK) {if (htNeedsResize(o->ptr)) dictResize(o->ptr);  // 满足条件则执行收缩
}
2. 计算目标大小
// dictResize() 逻辑
minimal = dict->ht[0].used;  // 当前实际元素数量
if (minimal < DICT_HT_INITIAL_SIZE)minimal = DICT_HT_INITIAL_SIZE;  // 最小收缩到初始大小
return dictExpand(dict, minimal);

可以看到这里不论是收缩还是扩容最后都调用了dictExpand方法 下面是该源码

int dictExpand(dict *d, unsigned long size) {return _dictExpand(d, size, NULL);
}/* * 扩展或收缩字典的哈希表* * @param d: 目标字典指针* @param size: 期望的新哈希表大小* @param malloc_failed: 内存分配失败标志(输出参数)* @return: 操作状态(DICT_OK 或 DICT_ERR)*/
int _dictExpand(dict *d, unsigned long size, int* malloc_failed) {// 初始化内存分配失败标志if (malloc_failed) *malloc_failed = 0;/* * 有效性检查:* 1. 如果字典正在 rehash 中,禁止扩展* 2. 如果当前元素数量大于目标大小,禁止收缩,这里的size就是目标扩容大小*/if (dictIsRehashing(d) || d->ht[0].used > size)return DICT_ERR;dictht n; /* 新哈希表 */// 计算新的size(调整为大于等于size的最小2的幂)unsigned long realsize = _dictNextPower(size);/* 如果新size与当前size相同,无需操作 */if (realsize == d->ht[0].size) return DICT_ERR;/* 分配新哈希表并初始化所有指针为NULL */n.size = realsize;n.sizemask = realsize-1;  // 掩码用于快速计算索引/* * 内存分配策略:* - 如果允许内存分配失败(malloc_failed != NULL),使用尝试分配* - 否则使用强制分配(失败会panic)*/if (malloc_failed) {n.table = ztrycalloc(realsize*sizeof(dictEntry*));*malloc_failed = n.table == NULL;  // 设置分配失败标志if (*malloc_failed)return DICT_ERR;} else {n.table = zcalloc(realsize*sizeof(dictEntry*));}n.used = 0;  // 初始化已用槽位数为0/* * 首次初始化处理(非rehash场景):* 当ht[0]为空时,直接使用新哈希表作为主表*/if (d->ht[0].table == NULL) {d->ht[0] = n;return DICT_OK;}/* * 准备渐进式rehash:* 1. 将新哈希表放入ht* 2. 设置rehashidx=0标记开始rehash* (图片中dictResize()最终会调用此逻辑)*/d->ht[1] = n;d->rehashidx = 0;  // 从索引0开始迁移数据return DICT_OK;
}

如图会更具当前的used来去判断此时的dictEntry的大小 并且为二的n次方,迁移的时候会将dictEntry从原本的地方一个一个迁移过来,全部迁移完过后ht[0]的table指针会指向新的dictEntry,ht[1]指向null

但是如果说一次性完成的话 如果Dict中包含的数据太多了,可能会导致主线程阻塞 因此DIct的rehash是多次完成的,如果是查询和修改那么ht[0]的dictEntry和ht[1]的dictEntry都需要去查询

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

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

相关文章

静态路由主备切换

在网络中&#xff0c;静态路由的主备切换是实现网络冗余的基础方案之一&#xff0c;通过配置不同优先级的静态路由&#xff0c;确保主用路径故障时&#xff0c;流量能自动切换到备用路径&#xff0c;提升网络可靠性。以下从知识讲解和实验配置两部分详细说明。一、静态路由主备…

PDF处理控件Aspose.PDF教程:在C#、Java、Python中快速缩小PDF

如果您的PDF太大&#xff0c;无法通过电子邮件发送&#xff0c;或者在线加载时间过长&#xff0c;您可以在几秒钟内缩小 PDF 大小。本教程介绍了借助Aspose.PDF使用 C#、Java 和 Python 编程快速缩小PDF的方法。 Aspose.PDF官方试用版下载 通过编程缩小 PDF 尺寸 如果您需要…

AWS EKS 常用命令大全:从基础管理到高级运维

前言 Amazon Elastic Kubernetes Service (EKS) 是 AWS 提供的托管 Kubernetes 服务,大大简化了 K8s 集群的部署和管理工作。作为 EKS 管理员或开发者,熟练掌握 kubectl 命令是日常工作的基础。本文将详细介绍 EKS 环境中常用的 kubectl 命令,涵盖集群管理、工作负载操作、…

GitHub Browser-Use 的部署失败记录:失败了,失败了。。。。

一、项目背景与核心作用 browser-use 是一个开源的浏览器自动化工具&#xff0c;通过集成 AI 智能体&#xff08;如 GPT、Claude、DeepSeek 等大型语言模型&#xff09;&#xff0c;实现用自然语言控制浏览器操作。其核心目标是 简化网页交互自动化&#xff0c;尤其适合复杂、…

调用springboot接口返回403,问题定位及总结

背景在一次与前端联调后端接口时前端返回接口返回状态码是403&#xff0c;前端返回说已经带了请求token。排查 查看后端控制台没有出现任何错误信息。自己postman手动调用接口&#xff0c;发现接口正常。仔细核对前端调用接口与postman请求的区别&#xff0c;没有发现任何问题。…

布隆过滤器原理分析、应用场景、与redis使用案例

一、核心结构与工作原理1.1 数据结构布隆过滤器由以下两部分组成&#xff1a;位数组&#xff08;Bit Array&#xff09;&#xff1a;一个长度为 m 的二进制数组&#xff0c;初始所有位为0。哈希函数组&#xff1a;k 个独立的哈希函数&#xff0c;每个函数将输入元素映射到位数组…

异步并发×编译性能:Dart爬虫的实战突围

Dart凭借其高效的异步并发模型、AOT编译性能和现代化的语法&#xff0c;正成为爬虫开发中值得关注的新选择。特别是对于Flutter应用开发者而言&#xff0c;Dart提供了一种"全栈同语言"的独特优势。 本文我将通过实战代码展示如何利用Dart的核心优势——包括基于Futur…

Day 8: 深度学习综合实战与进阶技术 - 从优化到部署的完整流程

Day 8: 深度学习综合实战与进阶技术 - 从优化到部署的完整流程 🎯 学习目标: 掌握深度学习模型优化、调试、迁移学习等工业级技能,能够构建高性能的深度学习应用 📚 核心概念概览 核心概念解释: 模型优化: 通过正则化、学习率调度等技术提升模型性能和泛化能力 为什么需…

特征工程--机器学习

1、特征工程1.1 概念特征工程&#xff08;Feature Engineering&#xff09;是机器学习项目中非常关键的一步&#xff0c;它是指通过领域知识来选择、创建或修改能够使机器学习模型更好地工作的特征&#xff08;即输入变量&#xff09;。特征工程的目标是提高模型的性能&#xf…

支持任意 MCP 协议的客户端

支持任意 MCP 协议的客户端&#xff08;如&#xff1a;Cursor、Claude、Cline&#xff09;可方便使用高德地图 MCP server。目前支持Streamable HTTP, SSE 和 Node.js I/O 三种接入方式(推荐用户使用Streamable HTTP)。 快速接入-MCP Server|高德地图API

【线性代数】目录

【线性代数】线性方程组与矩阵——&#xff08;1&#xff09;线性方程组与矩阵初步【线性代数】线性方程组与矩阵——行列式【线性代数】线性方程组与矩阵——&#xff08;2&#xff09;矩阵与线性方程组的解【线性代数】线性方程组与矩阵——&#xff08;3&#xff09;线性方程…

豆包新模型+PromptPilot:AI应用开发全流程实战指南

> 当深度推理的豆包大模型遇上智能提示词引擎,传统AI开发中**70%的调试时间被压缩至几分钟**,一场从“手工调参”到“智能优化”的开发范式革命正在发生。 ## 一、技术架构解析:双引擎驱动智能进化 ### 1.1 豆包新模型的技术突破 2025年火山引擎推出的**豆包1.6系列模型…

Day13 Vue工程化

1.介绍&环境准备 npm两项全局配置2.项目介绍&开发流程 npm create vue3.3.4 / install / run dev3.API风格 setup ref() onMounted()两种风格选项式API写法转为组合式API写法在根组件App.vue中引用写好的xxx.vue4.案例1.引入组件2.完整代码<script></script&g…

Linux中配置DNS

Linux中配置DNS服务 一、什么是DNS DNS (Domain Name System) 是域名服务 &#xff0c;它是由解析器和域名服务器组成的。 域名服务器是指保存有该网络中所有主机的域名和对应IP地址&#xff0c; 并具有将域名转换为IP地址功能的服务器。&#xff08;将网址解析成IP&#xff…

Redis应⽤-缓存与分布式锁

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Redis &#x1f525; 什么是缓存 缓存(cache)是计算机中的⼀个经典的概念.在很多场景中都会涉及到 核⼼思路就是把⼀些常⽤的数据放到触⼿可及 (访问速度更快) 的地⽅,⽅便随时读取 对于计算机…

TCP、HTTP/HTTPS、FTP 解析 + 面试回答参考

TCP、HTTP/HTTPS、FTP 解析 面试回答参考 在后端开发、网络编程以及运维面试中&#xff0c;TCP 协议、HTTP/HTTPS、FTP 是高频考点。本文将从原理、流程、面试常问问题出发&#xff0c;帮你一次性搞懂这些核心知识点。一、TCP 三次握手 1. 作用 建立可靠连接&#xff0c;确保双…

ATF(TF-A)安全通告 TFV-13(CVE-2024-7881)

安全之安全(security)博客目录导读 ATF(TF-A)安全通告汇总 目录 一、漏洞描述 二、缓解措施与建议 三、补丁修改 关于该漏洞的具体细节,可参考【CVE-2024-7881】ARM CPU漏洞安全通告】 Title 非特权上下文可以触发数据相关的预取引擎,从而获取特权位置的内容,并将这些…

Pytorch深度学习框架实战教程-番外篇02-Pytorch池化层概念定义、工作原理和作用

相关文章 视频教程 《Pytorch深度学习框架实战教程01》《视频教程》 《Pytorch深度学习框架实战教程02&#xff1a;开发环境部署》《视频教程》 《Pytorch深度学习框架实战教程03&#xff1a;Tensor 的创建、属性、操作与转换详解》《视频教程》 《Pytorch深度学习框架实战…

常见通信协议详解:TCP、UDP、HTTP/HTTPS、WebSocket 与 GRPC

常见通信协议详解&#xff1a;TCP、UDP、HTTP/HTTPS、WebSocket 与 RPC 在现代网络通信中&#xff0c;各种协议扮演着至关重要的角色&#xff0c;它们决定了数据如何在网络中传输、控制其可靠性、实时性与适用场景。对于开发者而言&#xff0c;理解这些常见的通信协议&#xff…

部署一个自己的音乐播放器教程

以下以部署 YesPlayMusic 为例&#xff0c;介绍两种常见的部署方法&#xff0c;一种是通过 Node.js 和 Git 在 Windows 系统上部署&#xff0c;另一种是通过 Docker 在 Linux 系统上部署。具体步骤如下&#xff1a;Windows 系统部署&#xff08;基于 Node.js 和 Git&#xff09…