Redis Set 数据结构演进史

从整数集合到哈希表的去重魔法

Redis 2.0

Set 诞生 + intset 初代

2010 年,Redis Set 类型诞生。底层使用 intset(整数集合)实现, 专为纯整数小集合设计,内存极其紧凑。

// intset 结构
uint32_t encoding 4 bytes
uint32_t length 4 bytes
int8_t contents[] 柔性数组
头部仅 8 bytes
纯整数存储 内存极致 二分查找 O(log n) 查询
Redis 2.2

dict 应对复杂数据

当 Set 包含非整数元素或元素数量超过 intset 阈值时, 自动转换为 dict(字典)实现。

// dictEntry (Set 场景)
void *key 成员
dictEntry *next 哈希冲突链
注意: Set 的 dict value 为 NULL
O(1) 单点操作 任意类型成员 无数量上限
Redis 2.4

编码转换阈值确立

确立 intset → dict 的转换阈值。当集合元素不超过 set-max-intset-entries 且全为整数时使用 intset,否则转为 dict。

// intset 限制条件
1. 元素数量 ≤ 512
2. 所有元素必须是 整数

任意一条不满足 → dict
自动编码转换 512 元素阈值
Redis 3.2

底层结构稳定

Set 的 intset/dict 双编码机制稳定成熟。 dict 作为 O(1) 查询的核心,支持 SISMEMBER 高效判断成员存在性。

// Set 核心操作
SADD: 添加成员 → O(1)
SISMEMBER: 判断存在 → O(1)
SREM: 删除成员 → O(1)
SUNION: 交集运算 → O(n)
O(1) 增删查 集合运算
Redis 6.2

阈值参数调整

将 set-max-intset-entries 默认值从 512 调整为 512(保持), 但优化了 intset 升级到 dict 的转换性能。

// Redis 6.2 配置
set-max-intset-entries 512
性能优化 参数保持
Redis 7.0+

set-max-intset-entries 扩大

Redis 7.0 将 set-max-intset-entries 扩大到 512(部分版本支持更大)。 intset 内部编码仍支持 int16_t/int32_t/int64_t 自动升级。

// intset 编码升级
int16_t: -32768 ~ 32767
int32_t: 自动升级
int64_t: 最大范围
触发: 元素超出当前编码范围
编码自动升级 类型安全 内存优化

intset ↔ dict 编码转换

Redis Set 根据数据特征自动选择最优编码

小整数集合: intset

适用条件:
• 元素全为整数
• 元素数 ≤ 512
encoding
length
...
...
4
3
1
2
5
紧凑连续内存 · 二分查找 O(log n)
✅ 内存极省 · ✅ 整数专用

大数据量/非整数: dict

触发条件:
• 包含非整数元素

• 元素数 > 512
a
m
-
z
-
k
哈希桶数组 · 链表解决冲突
✅ O(1) 查询 · ✅ 任意类型
// intset 编码升级流程
int16_t → 元素超范围 → int32_t
→ 元素超范围 → int64_t
→ 元素非整数 → dict(不可逆!)

Set 编码对比

内存占用

intset 极低
dict 中等

查询性能

intset O(log n)
dict O(1)

数据类型

intset 仅整数
dict 任意类型

数量上限

intset ≤ 512
dict 无上限

演进总结

Redis Set 的演进是一个"量体裁衣"的过程:

intset(纯整数小集合)⟷ dict(通用场景)

核心设计思想:
整数场景 → 用紧凑数组省内存(二分查找)
通用场景 → 用哈希表保性能(O(1) 查询)
自动切换 → 根据数据特征选择最优编码

这就是为什么 Set 既能存储"点赞用户 ID"(整数),也能存储"标签集合"(字符串), 而且都能保持高性能和低内存占用。