it编程 > 数据库 > Redis

Redis数据结构之Set结构详解

69人参与 2026-05-09 Redis

一、前言:无序、唯一、高效的集合

在 redis 的五大数据类型中,set(集合) 是一个非常独特且强大的存在。它天生具备两个核心特性:

基于这两个特性,set 被广泛应用于:

但你是否想过,redis 是如何在底层高效地实现“唯一性”和“快速查找”的?答案就藏在它的两种精巧数据结构中。

核心价值

redis set 的底层会根据数据特征,在 intset(整数集合)和 dict(字典)之间智能切换,以实现内存效率与操作性能的最佳平衡

本文将带你:

二、set 的双重身份:intset 与 dict

redis set 并非只有一种底层实现,而是拥有两种编码(encoding),由 redisobject 的 encoding 字段决定:

编码 (encoding)底层数据结构适用场景
obj_encoding_intsetintset (整数集合)所有元素都是整数,且数量较少
obj_encoding_htdict (字典/哈希表)元素包含非整数,或整数数量过多

这种设计体现了 redis “因地制宜” 的优化哲学:对简单、规则的数据用最省空间的结构;对复杂、庞大的数据用最高效的结构。

三、编码一:intset - 整数的极致压缩

3.1 诞生背景

当一个 set 中的所有元素都是整数时,使用通用的哈希表(dict)来存储显得有些“大材小用”。因为哈希表需要为每个元素存储一个完整的 dictentry 结构(包含 key, value, next 指针等),内存开销较大。

为了极致节省内存,redis 引入了 intset

3.2 源码结构

typedef struct intset {
    uint32_t encoding; // 编码方式:intset_enc_int16, intset_enc_int32, intset_enc_int64
    uint32_t length;   // 元素个数
    int8_t contents[]; // 柔性数组,存储实际的整数数据
} intset;

关键特性

3.3 类型升级机制(核心!)

intset 最精妙的设计在于其动态类型升级能力。

过程

注意:这个过程需要 o(n) 的时间复杂度和额外的内存,但只会在必要时发生一次,之后的插入操作又恢复 o(log n)(二分查找+插入)。

3.4 内存优势

假设一个 set 包含 1000 个 int16 范围内的整数:

内存节省高达 90% 以上!

四、编码二:dict - 通用的高性能解决方案

一旦 set 不再满足 intset 的苛刻条件(出现非整数,或整数太多),redis 会立即将其转换为 dict(字典)

4.1 为什么是 dict?

dict 是 redis 的基石数据结构之一,它是一个哈希表,天然具备以下特性:

4.2 dict 在 set 中的特殊用法

在 set 的场景下,dict 的使用非常巧妙:

// 伪代码示意
dict *d = dictcreate(&setdicttype, null);
dictadd(d, "element1", null);
dictadd(d, "element2", null);

✅ 优势:这样既利用了 dict 的高效哈希和唯一性保证,又省去了 value 的内存开销。

4.3 渐进式 rehash

dict 本身也有一套精妙的扩容/缩容机制(渐进式 rehash),确保在数据量巨大时,单次操作的延迟依然很低。这部分内容在此不展开,但它保证了即使 set 包含百万级元素,性能依然稳定。

五、编码转换:阈值与触发条件

redis 通过两个配置项来控制 set 何时从 intset 转换为 hashtable

配置项默认值说明
set-max-intset-entries512当 set 中的整数元素数量超过此值时,即使全是整数,也会转换为 dict。
隐式条件-当尝试向一个 intset 编码的 set 中插入一个非整数值(如字符串)时,会立即触发转换。

设计考量

六、动手实验:观察 set 的编码变化

6.1 验证 intset

# 添加纯整数
> sadd myset 1 2 3 100 200
(integer) 5

# 查看编码
> object encoding myset
"intset"

6.2 触发转换:插入非整数

# 插入一个字符串
> sadd myset "hello"
(integer) 1

# 编码已变为 hashtable
> object encoding myset
"hashtable"

6.3 触发转换:超过阈值

# 创建一个脚本,添加513个整数
> for i in {1..513}; do redis-cli sadd big_intset $i; done

# 查看编码(应为 hashtable)
> object encoding big_intset
"hashtable"

七、总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

您想发表意见!!点此发布评论

推荐阅读

Redis 双机部署完整方案(两种架构适配两台机器)

05-09

Redis中RPOP、BRPOP、LPOP和BLPOP使用示例代码

05-08

redis分布式设计的实现示例

05-08

Redis 延迟双删的实现示例

05-08

Redis的主从结构与哨兵机制详解

05-11

基于Redis实现订阅发布功能

05-12

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论