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

Redis 底层数据结构之 Dict(字典)

Redis 中的字典(Dict)是一种非常重要的数据结构,广泛用于实现数据库的键空间、哈希键等功能。它类似于 Java 中的 HashMap 或 Python 中的字典,是一种键值对存储结构,支持高效的增删改查操作。

一、Dict 的基本结构

Redis 的字典由三个主要结构组成:

  1. dict:整个字典的顶层结构
  2. dictType:字典的类型特定函数(多态支持)
  3. dictht:哈希表结构(实际存储键值对的地方)
  4. dictEntry:哈希表中的每个键值对节点

结构定义(简化版)

c

运行

// 字典类型函数
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;// 哈希表节点
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;     // 掩码,用于计算索引(size-1)unsigned long used;         // 已使用节点数量
} dictht;// 字典
typedef struct dict {dictType *type;             // 类型函数void *privdata;             // 私有数据dictht ht[2];               // 两个哈希表,用于渐进式 rehashlong rehashidx;             // rehash 索引,-1 表示未在进行 rehashunsigned long iterators;    // 当前运行的迭代器数量
} dict;

二、哈希表的实现原理

  1. 哈希计算

    • 对键进行哈希计算:hash = dict->type->hashFunction(key)
    • 计算索引:index = hash & dict->ht[x].sizemask(利用位运算高效计算)
  2. 解决哈希冲突

    • Redis 使用链地址法解决哈希冲突
    • 当多个键哈希到同一个索引时,通过链表将这些键值对连接起来
    • 新节点会被添加到链表的头部(O (1) 操作)

三、扩容(Rehash)机制

当哈希表中的键值对数量过多或过少时,Redis 会触发 rehash 操作来调整哈希表大小,以保证操作效率。

触发条件

  1. 扩容(expand)

    • 服务器没有执行 BGSAVE 或 BGREWRITEAOF 时,负载因子(used/size)> 1
    • 服务器正在执行 BGSAVE 或 BGREWRITEAOF 时,负载因子 > 5
    • 负载因子 = 哈希表已使用节点数 / 哈希表大小
  2. 缩容(shrink)

    • 负载因子 < 0.1 时,触发缩容

渐进式 Rehash 过程

为了避免一次性 rehash 带来的性能冲击,Redis 采用渐进式 rehash:

  1. 为 ht[1] 分配合适的大小(扩容通常是 ht[0].used*2 的第一个大于等于该值的 2^n)
  2. 设置 rehashidx = 0,表示开始 rehash
  3. 每次对字典执行增删改查时,除了操作本身,还会将 ht[0] 中 rehashidx 索引的所有键值对迁移到 ht[1]
  4. 迁移完成后,rehashidx 递增
  5. 当所有键值对迁移完成,rehashidx 设为 -1,释放 ht[0]ht[1] 成为新的 ht[0],并创建新的空 ht[1]

渐进式 Rehash 期间的操作处理

  • 查找:先在 ht[0] 查找,如未找到则到 ht[1] 查找
  • 插入:直接插入到 ht[1]
  • 删除:在 ht[0] 和 ht[1] 中查找并删除
  • 更新:在 ht[0] 中查找并更新,如不存在则到 ht[1] 中操作

四、增删改查操作的实现

1. 插入操作(dictAdd)

c

运行

int dictAdd(dict *d, void *key, void *val) {// 尝试查找键是否已存在dictEntry *entry = dictAddRaw(d, key, &existing);if (!entry) return DICT_ERR;  // 键已存在// 设置值dictSetVal(d, entry, val);return DICT_OK;
}dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) {int index;dictEntry *entry;dictht *ht;// 如果正在 rehash,先执行一步 rehashif (dictIsRehashing(d)) _dictRehashStep(d);// 计算键的索引if ((index = _dictKeyIndex(d, key, existing)) == -1)return NULL;// 确定要插入的哈希表(rehash 期间插入到 ht[1])ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];entry = zmalloc(sizeof(*entry));// 将新节点插入到链表头部entry->next = ht->table[index];ht->table[index] = entry;ht->used++;// 设置键dictSetKey(d, entry, key);return entry;
}

2. 查找操作(dictFind)

c

运行

dictEntry *dictFind(dict *d, const void *key) {dictEntry *he;unsigned int h, idx, table;// 如果哈希表为空,直接返回 NULLif (dictSize(d) == 0) return NULL;// 如果正在 rehash,先执行一步 rehashif (dictIsRehashing(d)) _dictRehashStep(d);// 计算哈希值h = dictHashKey(d, key);// 在两个哈希表中查找for (table = 0; table <= 1; table++) {idx = h & d->ht[table].sizemask;he = d->ht[table].table[idx];// 遍历链表查找while (he) {if (dictCompareKeys(d, key, he->key))return he;he = he->next;}// 如果不在 rehash,不需要检查第二个表if (!dictIsRehashing(d)) break;}return NULL;
}

3. 删除操作(dictDelete)

c

运行

int dictDelete(dict *d, const void *key) {// ... 省略部分代码// 计算哈希值和索引h = dictHashKey(d, key);// 在两个哈希表中查找并删除for (table = 0; table <= 1; table++) {idx = h & d->ht[table].sizemask;he = d->ht[table].table[idx];prev = NULL;while (he) {if (dictCompareKeys(d, key, he->key)) {// 从链表中移除节点if (prev)prev->next = he->next;elsed->ht[table].table[idx] = he->next;// 释放键值对dictFreeKey(d, he);dictFreeVal(d, he);zfree(he);d->ht[table].used--;return DICT_OK;}prev = he;he = he->next;}// 如果不在 rehash,不需要检查第二个表if (!dictIsRehashing(d)) break;}return DICT_ERR;  // 未找到要删除的键
}

4. 更新操作

Redis 中没有专门的更新函数,更新操作通常是先查找(dictFind),找到后直接修改值:

c

运行

dictEntry *entry = dictFind(d, key);
if (entry) {dictSetVal(d, entry, newValue);// 释放旧值(如果需要)// ...
}

五、Dict 的特点与优化

  1. 高效的哈希算法

    • 默认使用 MurmurHash2 算法,具有良好的分布性和计算速度
    • 对字符串、整数等不同类型的键有专门的优化
  2. 渐进式 rehash

    • 避免了一次性 rehash 带来的性能波动
    • 保证了 Redis 在高负载下的响应性
  3. 内存优化

    • 哈希表大小总是 2 的幂,便于使用位运算计算索引
    • 采用链表处理冲突,避免了开放地址法的聚集问题
  4. 多态支持

    • 通过 dictType 结构体支持不同类型的键值对
    • 可以自定义哈希函数、比较函数、复制函数等

六、总结

Redis 的字典(Dict)是一个高效、灵活的键值对存储结构,通过哈希表实现,并使用链地址法解决哈希冲突。其核心特性是渐进式 rehash 机制,这使得 Redis 能够在处理大量数据时保持高性能。

Dict 不仅是 Redis 数据库键空间的实现基础,也是哈希键、有序集合等数据类型的底层实现之一,理解 Dict 的工作原理对于深入掌握 Redis 内部机制至关重要。


文章转载自:

http://0IACKkKP.qkzdc.cn
http://sfSKqgwQ.qkzdc.cn
http://nnSxXXgw.qkzdc.cn
http://GzcQ6bEU.qkzdc.cn
http://zDTEMd1U.qkzdc.cn
http://COOElarM.qkzdc.cn
http://uTZjhuWm.qkzdc.cn
http://HiQimQRZ.qkzdc.cn
http://fi0AIDvS.qkzdc.cn
http://8lf76aQe.qkzdc.cn
http://Tg5cm9rE.qkzdc.cn
http://5YQbs1yE.qkzdc.cn
http://CWJmUtc8.qkzdc.cn
http://sIe01HBc.qkzdc.cn
http://589wpovL.qkzdc.cn
http://Btar0M14.qkzdc.cn
http://NsCuPhiE.qkzdc.cn
http://fGORIhkt.qkzdc.cn
http://VLD8xlxd.qkzdc.cn
http://6C8c7pNp.qkzdc.cn
http://qLNYqvD3.qkzdc.cn
http://vzCqSJwq.qkzdc.cn
http://BN0GF44K.qkzdc.cn
http://0yZRhnxh.qkzdc.cn
http://Yfu9MWs1.qkzdc.cn
http://6woKNFDY.qkzdc.cn
http://LcxDqb1e.qkzdc.cn
http://a2VH4tdT.qkzdc.cn
http://lRChwC1f.qkzdc.cn
http://v2HlQIgb.qkzdc.cn
http://www.dtcms.com/a/387051.html

相关文章:

  • UE 最短上手路线
  • 动手学Agent:Agent设计模式——构建有效Agent的7种模型
  • 苍穹外卖day01
  • 《LINUX系统编程》笔记p14
  • 可直接落地的pytest+request+allure接口自动化框架
  • 【精品资料鉴赏】267页政务大数据资源平台建设方案
  • 面试前端遇到的问题
  • 【深度学习计算机视觉】05:多尺度目标检测——从理论到YOLOv5实践
  • STM32 通过USB的Mass Storage Class读写挂载的SD卡出现卡死问题
  • 【Nginx开荒攻略】Nginx基本服务配置:从启动到运维的完整指南
  • 《漫威争锋》公布开发者愿景视频:介绍1.5版本的内容
  • Isight许可管理与其他软件集成的方法
  • 论文提纲:学术写作的“蓝图”,如何用AI工具沁言学术高效构建?
  • 快速解决云服务器的数据库PhpMyAdmin登录问题
  • 知识更新缺乏责任人会带来哪些风险
  • 容器化部署番外篇之Nexus3搭建私有仓库09
  • 计算机视觉(opencv)实战二十四——扫描答题卡打分
  • 居住证申请:线上照片回执办理!
  • Roo Code 的差异_快速编辑功能
  • 【深度学习】基于深度学习算法的图像版权保护数字水印技术
  • mcp初探
  • 深入C++对象生命周期:从构造到析构的奥秘
  • 视频上传以及在线播放
  • Powershell and Python are very similar
  • 鸿蒙Next离线Web组件实战:轻松实现离线加载与缓存优化
  • deepseek原理
  • 力扣复盘 之“移动零”
  • 任务管理系统常用平台整理:适合多项目团队
  • docker安装华为openGauss数据库
  • AI的设计图,神经网络架构