Redis数据结构-Dict

一、前言:Redis 的"心脏"------Dict

如果说 SDS 是 Redis 的基石,那么 Dict(字典) 就是其跳动的心脏 。它是 Redis 实现几乎所有高级数据结构的核心引擎

  • 数据库本身:就是一个巨大的 Dict,Key 是用户定义的键,Value 是各种数据类型对象。
  • Hash 类型:底层直接使用 Dict 存储 field-value 对。
  • Set 类型:当元素非整数或数量过大时,底层转为 Dict(Value 为 NULL)。
  • ZSet 类型:Dict 用于存储 member 到 score 的映射,实现 O(1) 查询。

💡 核心价值
Dict 是 Redis 高性能读写的基石,其精妙的哈希表实现和"渐进式 Rehash"机制,解决了大数据量下扩容导致的服务卡顿问题

本文将带你:

  • 拆解 Dict 的三层嵌套结构
  • 揭秘"渐进式 Rehash"如何做到无感扩容
  • 分析 Redis 如何在持久化期间智能控制内存增长

二、Dict 是什么?三层结构大起底

Redis 的 Dict 并非一个简单的哈希表,而是一个包含双哈希表、支持渐进式 Rehash 的复杂结构

2.1 核心组件

Dict 由三个核心结构体组成:

1. dictEntry:哈希节点
cpp 复制代码
typedef struct dictEntry {
    void *key;                  // 键
    union {                     // 值(支持多种类型)
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;     // 链表指针,解决哈希冲突
} dictEntry;
  • 作用:存储单个键值对。
  • 冲突解决 :采用链地址法(Separate Chaining)next 指针构成单向链表。
2. dictht:哈希表
cpp 复制代码
typedef struct dictht {
    dictEntry **table;          // 哈希桶数组(指针数组)
    unsigned long size;         // 哈希表大小(桶的数量,总是2的幂)
    unsigned long sizemask;     // 掩码 = size - 1,用于计算索引
    unsigned long used;         // 哈希表中已有节点的数量
} dictht;
  • 关键点size 总是 2 的幂 ,使得 index = hash & sizemask 可以代替取模运算,极大提升性能。
3. dict:字典主体
cpp 复制代码
typedef struct dict {
    dictType *type;             // 类型特定函数(如哈希函数、键比较函数等)
    void *privdata;             // 私有数据,传递给 type 中的函数
    dictht ht[2];               // **两个哈希表!用于渐进式 Rehash**
    long rehashidx;             // Rehash 索引,-1 表示未在 Rehash
    unsigned long iterators;    // 当前正在运行的安全迭代器数量
} dict;
  • 灵魂所在ht[2]rehashidx 共同实现了渐进式 Rehash

2.2 内存布局图

复制代码
+------------------+
|      dict        |
| +--------------+ |     +------------------+     +------------------+
| |   ht[0]      | |---->|   dictht (旧)    |---->| dictEntry 数组   |
| +--------------+ |     +------------------+     +------------------+
| |   ht[1]      | |---->|   dictht (新)    |---->| dictEntry 数组   |
| +--------------+ |     +------------------+     +------------------+
| rehashidx: n     |
+------------------+

三、Dict 的两大核心机制

3.1 机制 1:渐进式 Rehash(Incremental Rehashing)

问题 :传统哈希表在扩容/缩容时,需要一次性将所有键值对迁移到新表,这在大数据量下会导致服务长时间阻塞

Redis 的解决方案分而治之

  1. 准备阶段 :为 ht[1] 分配新空间(通常是 ht[0].used * 2)。
  2. 迁移阶段不一次性迁移 ,而是将迁移操作分散到后续的每一次 Dict 操作 (增删改查)中。
    • 每次操作时,顺带将 ht[0]rehashidx 位置上的所有键值对迁移到 ht[1]
    • 同时 rehashidx 自增。
  3. 完成阶段 :当 ht[0] 所有桶都迁移完毕,释放 ht[0],将 ht[1] 赋值给 ht[0],并重置 rehashidx = -1

优势

  • 无感扩容:避免了服务卡顿。
  • 平滑过渡 :查询时会同时在 ht[0]ht[1] 中查找,保证数据一致性。

3.2 机制 2:智能的扩容/缩容策略

Dict 的扩容/缩容并非简单地基于 used/size(负载因子),而是结合了服务器当前状态

触发条件
  • 扩容
    • 服务器没有 在执行 BGSAVEBGREWRITEAOF(即无子进程):
      • 负载因子 ≥ 1 时扩容。
    • 服务器正在 执行 BGSAVEBGREWRITEAOF
      • 禁止自动扩容!(防止写时复制 COW 导致大量内存页复制)
      • 除非 负载因子 ≥ 5(危险阈值),此时强制扩容以防性能急剧下降。
  • 缩容
    • 负载因子 < 0.1 时,进行缩容。

相关配置 (硬编码在 dict.c 中)

cpp 复制代码
static int dict_can_resize = 1; // BGSAVE/BGREWRITEAOF 时置为 0
static unsigned int dict_force_resize_ratio = 5; // 强制扩容阈值

⚠️ 设计哲学在内存效率和性能稳定性之间取得精妙平衡


四、Dict 在 Redis 中的应用场景

应用场景 Key Value 说明
数据库 用户定义的 Key redisObject 整个 Redis DB 就是一个 Dict
Hash 类型 Field Value 直接使用 Dict 作为底层
Set 类型 Member NULL 当 Set 不满足 intset 条件时
ZSet 类型 Member Score (double) 用于 O(1) 查询成员分数

五、动手实验:观察 Dict 的行为

5.1 查看 Hash 的底层编码

bash 复制代码
# 创建一个小 Hash
> HSET smallhash name "Alice" age "25"
(integer) 2

# 查看编码(小 Hash 可能用 ziplist)
> OBJECT ENCODING smallhash
"ziplist"

# 插入更多字段,使其转为 Dict
> HSET smallhash f1 v1 f2 v2 ... f512 v512
(integer) 512

# 再次查看(应变为 hashtable)
> OBJECT ENCODING smallhash
"hashtable"

5.2 观察 Rehash 过程(需开启 MONITOR)

虽然无法直接看到 rehashidx,但可以通过 INFO 命令间接感知:

bash 复制代码
# 在大量写入后,观察 used_memory 的变化趋势
# 如果内存是平滑增长而非突增,说明 Rehash 是渐进式的
> INFO memory

六、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
望舒3292 小时前
KMP算法
数据结构·算法
阿丰资源2 小时前
基于SpringBoot+MySQL的校园管理系统设计与实现(源码+文档+数据库,直接运行)
数据库·spring boot·mysql
cpp_25012 小时前
P2871 [USACO07DEC] Charm Bracelet S
数据结构·c++·算法·动态规划·题解·洛谷·背包dp
弹简特2 小时前
【Redis】01-认识Redis+分布式系统知识背景介绍
数据库·redis·缓存
2401_871492852 小时前
Vue.js计算属性computed依赖追踪与副作用函数effect关联机制
jvm·数据库·python
他们叫我阿冠2 小时前
SpringAI的基础学习
数据库·redis·缓存
2401_882273722 小时前
SQL如何快速提取分组中最晚时间点数据_结合窗口函数实现
jvm·数据库·python
2301_814809863 小时前
如何用 cookie 的 HttpOnly 与 Secure 属性防范 XSS 攻击
jvm·数据库·python