【Redis 初阶】Set 类型深度解析:去重集合的运算能力与实战场景

发布时间:2026/9/1 8:01:39
【Redis 初阶】Set 类型深度解析:去重集合的运算能力与实战场景 草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. Set 类型基本介绍二. Set 核心命令全解2.1 基础增删查sadd /smembers/sismember /scard2.2 随机操作spop /srandmember2.3 移动与删除smove /srem2.4 集合运算交、并、差三大操作2.5 命令小结三. Set 底层编码实现3.1 intset 整数集合3.2 hashtable 哈希表四. Set 典型应用场景4.1 用户标签系统4.2 共同好友与好友推荐4.3 UV 统计结尾前言前面我们依次拆解了 String、Hash、List 三种核心类型今天我们来看 Set 集合类型。很多开发者对 Set 的印象只停留在 “自动去重”但它真正的核心价值是原生支持集合交、并、差运算在标签系统、好友关系、UV 统计这类场景里比客户端自己写循环去重要高效得多。本文从基础特性讲起逐个拆解 Set 的核心命令与生产踩坑点深入 intset 和 hashtable 两种底层编码的设计逻辑再结合三个经典业务场景讲透实战用法带你从 “会用命令” 理解到 “为什么这么设计”。一. Set 类型基本介绍先澄清一个术语歧义Set在编程语境里有两个常见含义一是 “集合” 数据结构二是 “设置” 类操作比如 set 命令。本文讲的是集合数据结构类型。两个核心特性元素无序这里的无序是相对于 List 的 “位置有序” 而言的List 中元素调换顺序就是完全不同的列表Set 没有位置概念元素顺序不影响集合本身的同一性。比如{1,2,3}和{2,1,3}是同一个集合。注意这里的无序是 “不保证遍历顺序”不是 “随机排序”和 ZSet 按分数排序的有序有本质区别不要混淆。元素唯一不重复同一个元素在集合里只能存在一份写入重复元素会被自动忽略。这是 Set 最核心的特性也是绝大多数场景选用它的根本原因。Set 中的每个元素也都是字符串类型同样支持存储 JSON 等结构化字符串数据。二. Set 核心命令全解Set 相关命令都以S为前缀辨识度很高我们按功能分组逐一讲解。2.1 基础增删查sadd /smembers/sismember /scardsadd添加元素SADD key member[member...]向集合中添加一个或多个元素重复元素会被自动忽略。返回值是本次操作成功新增的元素个数。 时间复杂度O (k)k 为添加的元素个数。示例127.0.0.1:6379sadd tag:1javaredis mysql(integer)3# 重复添加 java返回 0127.0.0.1:6379sadd tag:1java(integer)0smembers获取所有元素SMEMBERS key返回集合中全部元素结果顺序不固定。 时间复杂度O (N)N 为集合元素总数。⚠️ 生产环境红线和keys *、hgetall同理大集合执行smembers会全量遍历长时间阻塞 Redis 主线程。如果需要遍历大集合应使用sscan渐进式遍历化整为零避免卡顿。sismember判断元素是否存在SISMEMBER key member判断指定元素是否在集合中存在返回 1不存在返回 0。 时间复杂度O(1)这是哈希表底层结构带来的核心优势也是去重判断效率极高的原因。scard获取元素总数SCARD key返回集合的元素总个数时间复杂度 O (1)。底层有专门的计数器记录长度直接读取即可不需要遍历。 这个命令非常实用比如统计 UV 总数时直接用 scard 就能拿到结果。2.2 随机操作spop /srandmemberspop随机弹出元素SPOP key[count]随机删除并返回集合中的一个或多个元素count 参数指定弹出数量默认弹出一个。 时间复杂度O (n)n 为弹出的元素个数。因为集合没有头尾的概念所以 pop 操作是完全随机的。官方文档明确承诺随机行为源码内部通过生成随机索引来实现。 非常适合抽奖、随机选题这类场景。srandmember随机获取元素SRANDMEMBER key[count]随机返回集合中的元素但只读取不删除。count 可以指定返回个数。 和 spop 的区别就是一个移除元素、一个保留元素适合随机推荐、随机展示这类不需要移除数据的场景。2.3 移动与删除smove /sremsmove移动元素SMOVEsourcedestination member原子性地将指定元素从源集合移动到目标集合。移动成功返回 1源集合中不存在该元素则返回 0。 时间复杂度O (1)。整个操作是原子的不会出现 “源集合删了、目标集合没加上” 的中间状态在数据迁移、状态变更场景中非常实用。即使目标集合已经存在该元素也会正常执行不会报错。srem删除元素SREM key member[member...]删除集合中一个或多个元素返回值是成功删除的元素个数。 时间复杂度O (k)k 为删除的元素个数。小提示Redis 不同命令的返回值规则差异很大不必死记硬背用的时候查官方文档最稳妥常用命令自然会记住。2.4 集合运算交、并、差三大操作这是 Set 最有价值的一组能力也是它区别于其他数据结构的核心功能。先明确三个基础概念交集同时出现在所有集合中的元素。例如 A{1,2,3,4}B{3,4,5,6}交集为 {3,4}并集所有集合的全部元素汇总重复元素只保留一份。上面例子的并集为 {1,2,3,4,5,6}差集在第一个集合存在、但在后续集合中不存在的元素。注意差集不满足交换律A-B 和 B-A 结果完全不同。上面例子中 A-B{1,2}B-A{5,6}每组运算都有两个版本直接返回结果的版本和将结果存入新集合的 store 版本。交集运算sinter /sinterstore# 直接返回交集结果SINTER key[key...]# 将交集结果存入目标集合返回交集元素个数SINTERSTORE destination key[key...]时间复杂度O (N*M)N 是最小集合的元素个数M 是最大集合的元素个数。示例127.0.0.1:6379sadd set11234127.0.0.1:6379sadd set23456127.0.0.1:6379sinter set1 set21)32)4# 把交集结果存到新集合 set3127.0.0.1:6379sinterstore set3 set1 set2(integer)2生产提醒多个大集合做交集运算开销较大可能阻塞主线程。尽量控制集合规模或者放在业务低峰期执行。并集运算sunion /sunionstore# 直接返回并集结果SUNION key[key...]# 将并集结果存入目标集合SUNIONSTORE destination key[key...]时间复杂度O (N)N 为所有集合的元素总数。差集运算sdiff /sdiffstore# 直接返回差集结果SDIFF key[key...]# 将差集结果存入目标集合SDIFFSTORE destination key[key...]时间复杂度O (N)N 为所有集合的元素总数。store 系列命令非常实用当运算结果需要后续继续使用时直接在服务端存成新集合省去客户端接收再回写的网络开销。2.5 命令小结命令作用时间复杂度sadd key member [member …]添加元素O (k)k 为添加个数srem key member [member …]删除元素O (k)k 为删除个数scard key获取元素总数O(1)sismember key member判断元素是否存在O(1)srandmember key [count]随机获取元素O (n)n 为返回个数spop key [count]随机弹出元素O (n)n 为弹出个数smembers key获取所有元素O (k)k 为元素总数sinter / sinterstore交集运算O(N*M)sunion / sunionstore并集运算O (k)总元素数sdiff / sdiffstore差集运算O (k)总元素数三. Set 底层编码实现Set 类型有两种底层编码会根据数据情况自动切换intset整数集合和hashtable哈希表。3.1 intset 整数集合当集合同时满足两个条件时使用 intset 编码集合中所有元素都是整数元素个数小于set-max-intset-entries配置默认值为 512。intset 是专门为整数集合设计的紧凑结构核心目标是节省内存。相比于哈希表它省去了指针、哈希桶这些额外开销用连续内存存储整数空间利用率非常高。3.2 hashtable 哈希表只要不满足 intset 的任意一个条件出现非整数元素、或元素数量超过阈值就会自动切换为 hashtable 编码。 底层和 Hash 类型的哈希表实现一致元素作为 key 存入哈希表value 为空利用哈希表的 O (1) 查找能力实现去重和快速存在性判断。做个横向对比避免和其他语言混淆C 的std::set底层是红黑树有序查找 O (logN)Java 的 Set 是接口常见实现有 HashSet哈希表无序和 TreeSet红黑树有序Redis 的 Set 底层是 intset 哈希表无序查找 O (1)。 Redis 中按分数排序的有序集合是 ZSet和 Set 是完全不同的两种数据结构。可以通过OBJECT encoding验证编码切换# 全是整数、数量少使用 intset127.0.0.1:6379sadd numset1234(integer)4127.0.0.1:6379OBJECT encoding numsetintset# 加入字符串触发编码转换127.0.0.1:6379sadd numset hello(integer)1127.0.0.1:6379OBJECT encoding numsethashtable源码视角intset 的空间优化智慧站在 C 开发的视角看intset 是非常经典的 “用少量性能换大量空间” 的工程优化案例。intset 的核心结构intset 本质是一个有序的整数数组大致结构如下typedefstructintset{uint32_tencoding;// 编码类型int16 / int32 / int64uint32_tlength;// 元素总个数int8_tcontents[];// 柔性数组存储实际整数元素}intset;三个关键设计点有序存储 二分查找元素按升序排列查找用二分法时间复杂度 O (logN)。虽然比哈希表的 O (1) 慢但元素少的时候差异可以忽略。编码自动升级小整数用 int16 存储加入大整数后自动整体升级为 int32、int64保证数组内元素类型一致同时尽可能节省空间。连续内存的代价插入删除需要移动后续元素O (N) 开销。元素少时开销可接受元素多了性能就不足以支撑因此超过阈值后切换为哈希表。这又是 Redis 一贯的设计哲学小数据量优先省内存大数据量优先保性能。极端的理论最优解往往工程成本极高合适的折中才是生产环境的最佳选择。集合运算的实现逻辑交集运算的大致思路非常朴素高效先找出元素最少的集合作为遍历基准遍历小集合的每个元素去其他集合中检查是否存在所有集合都存在的元素加入结果集。所以实际开销主要取决于最小集合的大小这也是时间复杂度和最小集合强相关的原因。生产环境做交集运算时尽量把小集合放在前面能有效降低整体计算量。四. Set 典型应用场景4.1 用户标签系统这是 Set 最经典的应用场景也是用户画像、千人千面推荐的基础。 每个用户的标签存入一个 Set天然自动去重添加删除都很方便。例如user:tag:1001 - {java, redis, 后端, 篮球}基于标签可以延伸出很多玩法用sinter计算两个用户的共同标签做用户相似度匹配用sunion合并多个用户的标签做群体画像反向也可以给每个标签建一个用户集合比如tag:java - {1001, 1002, ...}方便按标签圈选目标用户。当然标签系统深入做下去会有专门的用户画像团队而信息茧房也是这类系统带来的副作用 —— 算法总推送你偏好的内容视野会逐渐被限制在小圈子里。技术本身是中性的如何使用才是关键。4.2 共同好友与好友推荐社交产品的好友关系非常适合用 Set 存储。每个用户的好友列表存入一个 Setuser:friends:1001 - {1002, 1003, 1005}共同好友用sinter计算两个用户好友集合的交集直接得到共同好友列表好友推荐基于差集运算找出好友的好友中、你尚未添加的人作为 “可能认识的人” 推荐给你。这也是 QQ、微信同类功能的基础实现思路之一。4.3 UV 统计利用 Set 的自动去重特性可以非常方便地统计页面或产品的独立访客数UV。 先明确两个指标的区别PV页面浏览量每次访问都计数重复访问累加用incr即可实现UV独立访客数同一个用户多次访问只算一次需要按用户维度去重。实现方式将访问用户的 userId或 IP通过sadd写入对应页面的 Set 中自动去重。需要查看 UV 数值时直接scard就能拿到结果。# 用户 1001 访问首页sadd page:home:uv1001# 查询首页 UV 总数scard page:home:uv补充说明如果是超大规模 UV百万、千万级用 Set 存储全量用户 ID 会比较耗内存。这种场景更适合用 HyperLogLog 做概率统计牺牲极少量精度换取极大的空间节省。但中小规模的 UV 统计Set 完全够用且结果 100% 精确。核心考点总结核心特性元素无序、唯一不重复注意和 List 的位置有序、ZSet 的分数排序做好区分。核心命令基础增删查、随机操作、集合运算的用法与返回值含义store 版本命令的使用场景。生产风险大集合执行 smembers 会阻塞主线程多集合交集运算开销较高注意控制规模与执行时机。底层编码intset 与 hashtable 两种编码的适用条件、切换阈值intset 有序数组 编码升级的设计思路。应用场景用户标签、共同好友与好友推荐、UV 统计能说明实现思路和选用 Set 的原因。设计思想小数据量省空间、大数据量保性能的时空权衡用服务端集合运算替代客户端循环提升整体效率。结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语Set 的魅力不在于单个元素的增删改查而在于原生的集合运算能力能让我们用极简洁的方式解决标签、关系、去重这类典型业务问题。理解它的底层编码和适用边界做技术选型时才能更有把握。下一篇我们会讲解 Redis 最有特色的数据结构 ——ZSet 有序集合看看它在 Set 的基础上如何实现排序能力以及跳表的底层实现原理。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど