集合 Set
本教程共 40 篇 · 第 9 篇 · 更新于 2026-08-02
9. 集合 Set
本节目标:
- 理解 Set 是「无序 + 成员唯一」的 String 集合,增删查复杂度 O(1)
- 掌握写入/读取/判断成员:
SADD/SMEMBERS/SISMEMBER/SCARD/SREM- 掌握集合运算:交集
SINTER、并集SUNION、差集SDIFF及其STORE变体- 用共同好友、标签去重、随机抽奖等场景理解 Set 的用武之地
- 了解用
SSCAN替代SMEMBERS避免大集合阻塞
Set(集合)是 Redis 中一组无序且成员唯一的 String 集合。所谓「唯一」,是指同一个成员(member)在集合里最多出现一次;你重复 SADD 同一个值,第二次会被忽略,返回 0。所谓「无序」,是指集合不保证遍历顺序,每次 SMEMBERS 返回的顺序都不应被依赖(底层由哈希表或整数集维护)。
和集合相关的几个事实:
- 底层编码可能是
intset(成员全是整数且数量较少时)或hashtable,Redis 自动选择,对使用者完全透明。 - 单个集合最多可容纳 2^32 - 1(约 42 亿)个成员。
- 添加、删除、查找成员的复杂度都是 O(1)。
9-1 写入与读取
最基础的写入命令是 SADD,可以一次添加一个或多个成员:
命令:
127.0.0.1:6379> SADD tags:article:100 "redis" "database" "cache"
输出:
(integer) 3
返回值表示「本次新加入的成员数量」。如果某个成员已存在,它不会被重复计入。我们再尝试加入一个已存在和一个新的:
命令:
127.0.0.1:6379> SADD tags:article:100 "redis" "nosql"
输出:
(integer) 1
redis 已经存在,只有 nosql 是新成员,所以返回 1。
读取全部成员用 SMEMBERS:
命令:
127.0.0.1:6379> SMEMBERS tags:article:100
输出:
1) "redis"
2) "database"
3) "cache"
4) "nosql"
判断某个成员是否在集合中用 SISMEMBER,获取成员数量用 SCARD:
命令:
127.0.0.1:6379> SISMEMBER tags:article:100 "redis"
127.0.0.1:6379> SCARD tags:article:100
输出:
(integer) 1
(integer) 4
SISMEMBER 返回 1 表示存在、0 表示不存在,比先 SMEMBERS 再在客户端遍历要高效得多(始终 O(1))。
删除成员用 SREM,随机弹出一个成员用 SPOP,随机取若干个但不删除用 SRANDMEMBER:
命令:
127.0.0.1:6379> SREM tags:article:100 "cache"
127.0.0.1:6379> SRANDMEMBER tags:article:100 2
127.0.0.1:6379> SPOP tags:article:100
输出:
(integer) 1
1) "redis"
2) "nosql"
"database"
9-2 集合运算:交集、并集、差集
Set 最强大的能力是集合间的数学运算,这正是「关系型数据里要用 JOIN 才能算的活」在 Redis 里一条命令搞定。
假设有两个用户的好友集合:
命令:
127.0.0.1:6379> SADD friends:alice "bob" "carol" "dave"
127.0.0.1:6379> SADD friends:bob "alice" "carol" "erin"
输出:
(integer) 3
(integer) 3
交集 SINTER——两人共同的好友:
命令:
127.0.0.1:6379> SINTER friends:alice friends:bob
输出:
1) "carol"
并集 SUNION——两人合计认识的所有人:
命令:
127.0.0.1:6379> SUNION friends:alice friends:bob
输出:
1) "bob"
2) "carol"
3) "dave"
4) "alice"
5) "erin"
差集 SDIFF——Alice 认识但 Bob 不认识的人(顺序敏感,A 减 B):
命令:
127.0.0.1:6379> SDIFF friends:alice friends:bob
输出:
1) "bob"
2) "dave"
这些运算都有对应的 STORE 变体(SINTERSTORE / SUNIONSTORE / SDIFFSTORE),把结果直接存成一个新 key,便于缓存或后续复用:
命令:
127.0.0.1:6379> SINTERSTORE mutual:alice:bob friends:alice friends:bob
输出:
(integer) 1
提示:当集合很大时,
SMEMBERS会一次性返回全部成员,可能阻塞服务器。生产环境遍历大集合请用SSCAN(用法与SCAN一致,带游标分批返回),第 04 章已介绍SCAN思路。Redis 8.x 在集合运算的执行效率上持续做了优化,但对超大集合求交集仍要注意耗时。
9-3 实战场景
场景一:共同好友 / 共同关注。 如上面例子,把每个用户的关系存成 Set,求交集即得「你可能认识的人」「共同好友」。微博、抖音的「共同关注」就是这类运算。
场景二:标签与去重。 给文章、商品打标签,把标签存成 Set,天然去重;想找「同时带 redis 和 cache 标签的文章」,对多篇文章的标签集合求交集即可。
场景三:随机抽奖 / 抽签。 SPOP 随机且不放回地弹出成员,非常适合「从奖池抽 N 个中奖者」;SRANDMEMBER 则是「抽但先不移除」,适合「随机推荐」「随机展示」。
场景四:轻量独立名单。 既要去重、又想随时列出具体成员(例如「本日访问过该页面的用户名单」),用 Set 存用户 ID 再 SCARD 取人数最直接;若只关心「大约有多少人」而不需要名单,第 12 章的 HyperLogLog 更省内存。
9-4 常见误区
- 误区:集合有序。 Set 是无序的,
SMEMBERS的顺序不可依赖;若需要排序或带分数的集合,请看下一章「有序集合 Sorted Set」。 - 误区:SADD 重复添加会报错。 不会报错,重复成员被忽略,返回值只统计新加入的数量。
- 误区:SINTER 比在应用层循环判断快不了多少。 集合运算在服务端一次性完成,避免了把大量数据拉到应用层再比较的网络与计算开销,差距是数量级的。
- 误区:SMEMBERS 能安全遍历百万级集合。 它会一次性返回全部成员(O(N)),大集合请改用
SSCAN。
9.x Set 与其他类型的边界
初学者常混淆 Set、List 与 Sorted Set,记住一句口诀即可:List 是「有序可重复」的队列,Set 是「无序且去重」的集合,Sorted Set 是「按分数排序且去重」的集合。例如「用户最近浏览记录」允许重复、强调顺序,用 List;「用户收藏夹」不重复,用 Set;「商品热度榜」需要排序,用 Sorted Set。
Set 的成员只能是 String,不能嵌套结构。虽然单个集合容量极大(约 42 亿成员),但 SINTER / SUNION / SDIFF 的耗时与参与集合的总成员数相关。当集合非常庞大时,建议采取三条措施:一、尽量在从库执行集合运算,避免影响主库响应;二、用 *STORE 把运算结果缓存成一个新 key,供后续反复读取,避免每次都重算;三、对超大集合先做采样或按业务分片,缩小单次运算规模。Redis 8.x 对这些集合运算做了持续的内部优化,但容量规划仍要把大集合运算的成本考虑进去,不能假设它永远是 O(1)。
此外,Set 非常适合做「关系型数据里要用 SQL 的 JOIN + DISTINCT 才能算的活」:共同好友、共同标签、差集推荐,本质都是集合运算。把关系存成 Set 后,一条 SINTER 就取代了应用层把两份大列表拉回来循环比对的笨办法,网络与计算开销都小得多。
9-5 小结
- Set 是无序、成员唯一的 String 集合,增删查 O(1),最大约 42 亿成员。
- 写入/读取:
SADD/SMEMBERS/SISMEMBER/SCARD/SREM。 - 随机:
SPOP(弹出) /SRANDMEMBER(不弹出)。 - 集合运算:交集
SINTER、并集SUNION、差集SDIFF,及对应的*STORE持久化变体。 - 用武之地:共同好友、标签去重、随机抽奖、轻量独立名单。
- 大集合遍历用
SSCAN而非SMEMBERS。
提示:关于版本——本教程正文统一以 Redis 8.x(最新稳定版)为准;官方 redis.io 下载页另标 8.8 为 “Latest stable”,而 GitHub 上 redis/redis 的最新发布 tag 为 8.10.0,二者同属 8.x,命令与类型差异对教材影响极小。