作为Redis五大基础数据结构(String、List、Hash、Set、ZSet)之一,Set(集合) 凭借"无序、唯一、高效集合运算"的特性,在开发中扮演着重要角色。无论是去重存储、共同好友计算,还是随机抽奖场景,它都能轻松搞定。今天咱们就从底层原理到实战应用,彻底搞懂这个"宝藏"数据结构!
一、Set的底层逻辑:为什么能又快又省内存?
很多人用Set时只关注命令,却忽略了它的底层实现——这就像开车只看导航不研究发动机。Redis的Set会根据元素类型和数量自动切换底层结构,核心是两种设计:intset
(整数集合)和hashtable
(哈希表)。
1.1 小整数场景:intset——紧凑到"抠门"的存储
如果你的Set里全是整数,且元素数量不超过intset-max-intset-entries
(默认512),Redis会用intset
来存。它本质上是一个有序的整数数组,但会根据元素大小动态升级编码,超省内存!
举个栗子🌰:
假设你存了一堆小整数(比如1~100),Redis会用int16_t
(2字节)存;如果突然存了个大整数(比如30000),它会自动升级为int32_t
(4字节),并把之前的小整数也"扩容"成32位——这就是动态升级。升级后虽然会多占点内存,但后续插入更大整数就不用反复扩容了,平衡了性能和空间。
特点总结:
✅ 元素有序(升序排列,方便快速查找)
✅ 内存紧凑(相同类型整数共享存储)
✅ 自动升级(兼容小→大整数)
1.2 混合/大数量场景:hashtable——万能的键值对
如果Set里的元素不是整数,或者数量超过了intset-max-intset-entries
,Redis会用hashtable
(也就是字典)。这时候,哈希表的键(key)是Set的元素,值(value)统一是一个空对象(NULL
)——相当于用哈希表"标记"元素存在。
比如你存了"apple"
、"banana"
这样的字符串,哈希表的结构就是:
{"apple": NULL, "banana": NULL}
特点总结:
✅ 支持任意字符串元素(二进制安全)
✅ 查找/插入/删除O(1)时间复杂度(平均情况)
✅ 元素完全无序(哈希表的天然特性)
二、Set的"核心技能":这些命令你必须会!
学会命令才是实战的关键。Set的命令分为基础操作、集合运算和高级遍历三大类,咱们逐个看:
2.1 基础操作:增删改查
命令 | 功能 | 示例 | 说明 |
---|---|---|---|
SADD key member... | 添加元素(重复自动忽略) | SADD fruits apple banana | 返回新增元素数量(比如之前没有apple,返回1) |
SREM key member... | 删除元素(不存在自动忽略) | SREM fruits apple | 返回删除元素数量(成功删1个返回1) |
SISMEMBER key member | 判断元素是否存在 | SISMEMBER fruits banana | 存在返回1,不存在返回0 |
SMEMBERS key | 获取所有元素(无序) | SMEMBERS fruits | 结果可能是banana 或apple ,顺序不固定 |
SCARD key | 统计元素数量 | SCARD fruits | 直接返回当前集合大小 |
SPOP key [count] | 随机删除并返回元素 | SPOP fruits 2 | 从集合里随机删2个,返回被删的元素(适合抽奖) |
SRANDMEMBER key [count] | 随机获取元素(不删除) | SRANDMEMBER fruits 1 | 随机返回1个元素,适合"随机推荐"场景 |
SMOVE source dest member | 把元素从一个集合"搬"到另一个 | SMOVE fruits basket apple | 成功返回1,失败(元素不存在)返回0 |
2.2 集合运算:交集/并集/差集,一键搞定!
Set最强大的地方在于高效的集合运算,比如计算共同好友、商品共同标签等,用Redis命令比自己写代码快10倍!
命令 | 功能 | 示例 | 应用场景 |
---|---|---|---|
SINTER key... | 计算多个集合的交集(都存在的元素) | SINTER friends:A friends:B | 找A和B的共同好友 |
SINTERSTORE dest key... | 计算交集并存到新集合 | SINTERSTORE common friends:A friends:B | 把共同好友存到common 集合 |
SUNION key... | 计算多个集合的并集(至少一个存在的元素) | SUNION fruits1 fruits2 | 合并两个水果集合的去重结果 |
SUNIONSTORE dest key... | 计算并集并存到新集合 | SUNIONSTORE all_fruits fruits1 fruits2 | 合并结果存到all_fruits |
SDIFF key... | 计算差集(第一个集合有,其他集合没有的元素) | SDIFF fruits1 fruits2 | 找fruits1 独有的水果 |
SDIFFSTORE dest key... | 计算差集并存到新集合 | SDIFFSTORE only_in_A fruits1 fruits2 | 把fruits1 独有的元素存到only_in_A |
2.3 高级遍历:大数据量也不怕!
如果Set元素太多(比如10万+),直接用SMEMBERS
会卡死——这时候必须用SSCAN
,它是游标式遍历,支持分页和模糊匹配!
命令格式:SSCAN key cursor [MATCH pattern] [COUNT count]
cursor
:游标(初始为0,遍历完返回0)MATCH pattern
:模糊匹配(比如*apple
找含"apple"的元素)COUNT count
:每次遍历的数量(默认10,可调大提高效率)
示例:
# 从游标0开始,找所有含"a"的元素,每次查10个
SSCAN fruits 0 MATCH *a* COUNT 10
三、实战场景:Set能帮你解决哪些问题?
3.1 去重存储:记录用户的"行为痕迹"
比如记录用户点赞过的文章,用Set天然去重,避免重复记录:
# 用户1001点赞文章1和2(重复点赞自动忽略)
SADD user:1001:liked_articles article:1 article:2 article:1
# 查看用户点赞过的文章
SMEMBERS user:1001:liked_articles # 返回 {article:1, article:2}
3.2 共同好友/兴趣:社交场景的"神器"
假设用户A的好友集合是friends:A
,用户B的是friends:B
,找共同好友只需一个命令:
SINTER friends:A friends:B # 返回同时在两个集合中的好友ID
3.3 随机抽奖:保证不重复中奖!
抽奖时最怕重复中奖,用SPOP
直接随机删元素,完美解决:
# 初始化10个候选用户
SADD lottery:candidates user1 user2 ... user10
# 抽3个中奖者(直接从集合里删掉,避免重复)
SPOP lottery:candidates 3 # 返回3个中奖用户ID
3.4 标签系统:快速查询"多标签关联"的内容
给文章打标签时,用Set存储每个标签对应的文章ID,查询同时包含多个标签的文章用SINTER
:
# 给文章101打标签"redis"和"数据库"
SADD tag:redis article:101
SADD tag:database article:101
# 查询同时被打上"redis"和"数据库"标签的文章
SINTER tag:redis tag:database # 返回 {article:101}
四、避坑指南:使用Set的3个注意事项!
4.1 大Key警告:元素太多会变慢!
如果Set元素超过10万,SMEMBERS
、SINTER
这类全量操作会非常慢(因为要遍历整个集合)。解决方案:
- 拆分集合:按时间或业务维度分片(比如
fruits:2024
、fruits:2025
); - 避免全量操作:用
SSCAN
代替SMEMBERS
,分批处理数据; - 监控大Key:用
redis-cli --bigkeys
扫描,及时拆分。
4.2 内存优化:小整数集合更省空间!
如果Set存的是小整数(比如1~10000),Redis会自动用intset
存储,比hashtable
省很多内存。可以通过配置intset-max-intset-entries
调整阈值(默认512),但别改太小,否则频繁升级编码反而影响性能。
4.3 持久化影响:大集合RDB生成慢!
Set的持久化(RDB/AOF)和Redis整体机制一致,但大集合生成RDB快照时,fork子进程会占用较多内存和时间。建议:
- 大集合尽量用
hashtable
(元素非整数或数量大); - 生产环境合理配置RDB快照频率(比如
save 900 1
)。
总结:Set是"万能工具箱",用对场景超高效!
Redis Set的核心优势是唯一性+集合运算,底层通过intset
和hashtable
自动切换,兼顾内存和性能。无论是去重存储、共同好友计算,还是随机抽奖,它都能轻松搞定。记住:
✅ 小整数用intset
(省内存);
✅ 混合类型/大数量用hashtable
(功能全);
✅ 大Key要拆分,避免全量操作;
✅ 集合运算(交/并/差)是秒杀其他方案的利器!
下次遇到需要"去重+集合运算"的场景,直接上Set准没错!赶紧打开Redis客户端,试试这些命令吧~ 😊
点赞、收藏+关注,再来不迷路~