作为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,不存在返回0SMEMBERS 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,失败(元素不存在)返回02.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_fruitsSDIFF key...计算差集(第一个集合有,其他集合没有的元素)SDIFF fruits1 fruits2找fruits1独有的水果SDIFFSTORE dest key...计算差集并存到新集合SDIFFSTORE only_in_A fruits1 fruits2把fruits1独有的元素存到only_in_A2.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客户端,试试这些命令吧~ 😊
点赞、收藏+关注,再来不迷路~