引言
Redis的Set(集合)类型是一个无序的、元素不重复的数据结构,它基于哈希表实现,类似于C++中的unordered_set。Set类型在Redis中提供了高效的集合运算能力,特别适合处理需要去重、求交并差集的场景。本篇博客将深入探讨Redis Set的底层原理、核心命令和多种实用场景。
一、Redis Set核心特性
1.1 基本特性
Redis Set
特点 无序存储 元素唯一 高效集合运算 不保证插入顺序 不保证排序顺序 自动去重 插入重复元素返回0 交集sinter 并集sunion 差集sdiff
1.2 与List的对比
| 特性 | Set | List |
|---|---|---|
| 顺序性 | 无序 | 有序(插入顺序) |
| 重复性 | 不允许重复 | 允许重复 |
| 访问方式 | 不支持下标访问 | 支持下标访问 |
| 底层实现 | 哈希表、intset | QuickList |
| 主要用途 | 去重、集合运算 | 队列、栈、时间线 |
二、Redis Set核心命令详解
2.1 基本操作命令
SADD - 添加元素
redis
# 语法
SADD key member [member ...]
# 示例
SADD tags:user:1001 "tech" "music" "sports"
# 返回:3(成功添加的元素个数)
# 添加重复元素
SADD tags:user:1001 "tech" # 返回:0(元素已存在)
SMEMBERS - 获取所有元素
redis
# 语法
SMEMBERS key
# 示例
SMEMBERS tags:user:1001
# 可能返回:"tech", "music", "sports"(顺序不确定)
# ⚠️ 警告:时间复杂度O(N),大集合需谨慎使用
SISMEMBER - 检查元素存在
redis
# 语法
SISMEMBER key member
# 示例
SISMEMBER tags:user:1001 "tech" # 返回:1(存在)
SISMEMBER tags:user:1001 "art" # 返回:0(不存在)
SCARD - 获取元素个数
redis
# 语法
SCARD key
# 示例
SADD tags:user:1001 "tech" "music" "sports"
SCARD tags:user:1001 # 返回:3
# ⚠️ 时间复杂度:O(1)
2.2 删除与移动操作
SPOP - 随机删除并返回
redis
# 语法
SPOP key [count]
# 示例
SADD lottery "user1" "user2" "user3" "user4" "user5"
SPOP lottery 2 # 随机删除并返回2个元素
# 可能返回:"user3", "user1"
SREM - 删除指定元素
redis
# 语法
SREM key member [member ...]
# 示例
SADD tags:user:1001 "tech" "music" "sports" "art"
SREM tags:user:1001 "art" "music" # 删除两个元素
# 返回:2(成功删除的个数)
SMOVE - 移动元素
redis
# 语法
SMOVE source destination member
# 示例
SADD blacklist "user:1001" "user:1002"
SADD whitelist "user:1003"
# 将user:1001从黑名单移到白名单
SMOVE blacklist whitelist "user:1001"
# 返回:1(移动成功)
2.3 随机操作
SRANDMEMBER - 随机获取元素
redis
# 语法
SRANDMEMBER key [count]
# 示例
SADD colors "red" "green" "blue" "yellow" "purple"
# 获取1个随机元素
SRANDMEMBER colors # 可能返回:"green"
# 获取3个随机元素(可能重复)
SRANDMEMBER colors 3
# 获取3个不重复的随机元素(负数count)
SRANDMEMBER colors -3
三、集合运算命令
3.1 交集运算
Set A: 1,2,3,4 交集运算 Set B: 3,4,5,6 结果: 3,4
SINTER - 计算交集
redis
# 语法
SINTER key [key ...]
# 示例:共同好友
SADD friends:user1 "user2" "user3" "user4" "user5"
SADD friends:user2 "user1" "user3" "user6" "user7"
# 计算user1和user2的共同好友
SINTER friends:user1 friends:user2
# 返回:"user3"
SINTERSTORE - 计算并存储交集
redis
# 语法
SINTERSTORE destination key [key ...]
# 示例:存储共同好友
SINTERSTORE common:user1:user2 friends:user1 friends:user2
# 返回:1(交集元素个数)
SMEMBERS common:user1:user2 # 返回:"user3"
3.2 并集运算
Set A: 1,2,3,4 并集运算 Set B: 3,4,5,6 结果: 1,2,3,4,5,6
SUNION - 计算并集
redis
# 语法
SUNION key [key ...]
# 示例:合并标签
SADD tags:article1 "redis" "database" "nosql"
SADD tags:article2 "redis" "cache" "performance"
# 合并两篇文章的所有标签
SUNION tags:article1 tags:article2
# 返回:"redis", "database", "nosql", "cache", "performance"
SUNIONSTORE - 计算并存储并集
redis
# 语法
SUNIONSTORE destination key [key ...]
# 示例:创建标签池
SUNIONSTORE all:tags tags:article1 tags:article2
SCARD all:tags # 返回:5
3.3 差集运算
Set A: 1,2,3,4 A - B Set B: 3,4,5,6 结果: 1,2 Set B: 3,4,5,6 B - A Set A: 1,2,3,4 结果: 5,6
SDIFF - 计算差集
redis
# 语法
SDIFF key [key ...]
# 示例:个性化推荐
SADD user:1001:viewed "movie1" "movie2" "movie3" "movie4"
SADD all:movies "movie1" "movie2" "movie3" "movie4" "movie5" "movie6"
# 推荐用户没看过的电影
SDIFF all:movies user:1001:viewed
# 返回:"movie5", "movie6"
SDIFFSTORE - 计算并存储差集
redis
# 语法
SDIFFSTORE destination key [key ...]
# 示例:创建推荐列表
SDIFFSTORE recommendations:user:1001 all:movies user:1001:viewed
SCARD recommendations:user:1001 # 返回:2
四、Redis Set底层实现
4.1 编码方式
Redis Set使用两种编码方式,根据数据特性自动选择:
IntSet(整数集合)
redis
# 当集合满足以下条件时使用IntSet:
# 1. 所有元素都是整数
# 2. 元素数量较少(默认512个)
# 3. 整数在64位有符号范围内
# 示例:使用IntSet
SADD small:numbers 1 2 3 4 5
OBJECT ENCODING small:numbers # 返回:"intset"
HashTable(哈希表)
redis
# 当不满足IntSet条件时使用HashTable
# 示例:使用HashTable
SADD large:set "string1" "string2" 100 # 包含非整数
OBJECT ENCODING large:set # 返回:"hashtable"
4.2 内存优化配置
redis
# Redis配置文件中Set相关参数
set-max-intset-entries 512 # IntSet最大元素数量
# 当元素数量超过此值或包含非整数时,转换为HashTable
五、Redis Set应用场景
5.1 用户标签系统
用户标签存储 标签: tech,music,sports 标签: tech,art,travel 标签: music,sports,food 标签集合 用户1001 标签集合 用户1002 标签集合 用户1003 基于标签的推荐 计算交集找共同兴趣 计算差集找互补兴趣 计算并集找热门标签
实现代码:
redis
# 为用户添加标签
SADD user:1001:tags "tech" "programming" "music" "basketball"
SADD user:1002:tags "tech" "music" "travel" "photography"
SADD user:1003:tags "music" "sports" "food" "travel"
# 1. 找共同兴趣好友(交集)
SINTER user:1001:tags user:1002:tags
# 返回:"tech", "music" → 推荐user1002给user1001
# 2. 找可能感兴趣的新标签(差集)
SDIFF user:1002:tags user:1001:tags
# 返回:"travel", "photography" → 推荐给user1001
# 3. 热门标签统计(并集)
SUNIONSTORE hot:tags user:1001:tags user:1002:tags user:1003:tags
5.2 共同好友系统
用户1002的好友 用户1001的好友 SINTER SINTER friends:user:1002
Set 元素: user:1001, user:1003, user:1005 friends:user:1001
Set 元素: user:1002, user:1003, user:1004 交集运算 common:user:1001:user:1002
结果Set 共同好友: user:1003 推荐系统 推荐逻辑:
- 共同好友越多,推荐权重越高
- 二度人脉推荐
- 兴趣相似度匹配
redis
# 存储好友关系
SADD friends:user:1001 "user:1002" "user:1003" "user:1004" "user:1005"
SADD friends:user:1002 "user:1001" "user:1003" "user:1006" "user:1007"
# 计算共同好友
SINTER friends:user:1001 friends:user:1002
# 返回:"user:1003" → 有1个共同好友
# 存储共同好友结果
SINTERSTORE common:user:1001:user:1002 friends:user:1001 friends:user:1002
SCARD common:user:1001:user:1002 # 返回:1
# 好友推荐:用户可能认识的人
# user:1001的朋友中,不是user:1002朋友的人
SDIFF friends:user:1001 friends:user:1002
# 返回:"user:1002", "user:1004", "user:1005"
# 去掉user:1002(自己),推荐user:1004和user:1005给user:1002
5.3 UV统计(独立访客)
Redis Set 存储层 访问记录层 SADD SADD SADD SADD SCARD SUNIONSTORE SUNIONSTORE SUNIONSTORE SCARD uv:daily:2024-01-15 uv:daily:2024-01-16 uv:daily:2024-01-17 uv:weekly:2024-02 uv:monthly:2024-01 访问请求1
user:1001 访问请求2
user:1002 访问请求3
user:1001 访问请求4
user:1003 当日UV: 3
(user:1001只计1次) 周UV: 去重统计
redis
# 每日UV统计方案
# 1. 记录每日访问用户
SADD uv:2024-01-15 "user:1001" "user:1002" "user:1003" "user:1001"
# user:1001重复访问,但只记录一次
# 2. 获取当日UV
SCARD uv:2024-01-15 # 返回:3
# 3. 每周UV统计(合并7天数据)
SUNIONSTORE uv:week:2024-02 "uv:2024-01-15" "uv:2024-01-16" "uv:2024-01-17"
SCARD uv:week:2024-02 # 返回:去重后的总UV
# 4. 按月统计
SUNIONSTORE uv:month:2024-01 "uv:2024-01-*" # 需要收集所有key
六、性能优化与最佳实践
6.1 大集合处理策略
redis
# 问题:SMEMBERS处理大集合会阻塞
# 解决方案1:使用SSCAN分批获取
SSCAN large:set 0 COUNT 100 # 分批获取,每次100个
# 解决方案2:拆分为多个小集合
# 按前缀或哈希分片
SADD set:part:1 "item1" "item2"
SADD set:part:2 "item3" "item4"
# 需要时合并查询
6.2 集合运算性能优化
redis
# 1. 使用*STORE命令减少网络传输
# 不佳:客户端计算
SINTER set1 set2 set3 # 返回大量数据到客户端
# 优化:服务器端存储
SINTERSTORE result set1 set2 set3 # 结果存在Redis中
SMEMBERS result # 需要时再获取
# 2. 预估集合大小
SCARD set1 # O(1)操作,先了解数据规模
SCARD set2
# 3. 合理排序集合参数
# 差集不满足交换律,小集合在前更高效
# set1有10个元素,set2有1000个元素
SDIFF set1 set2 # 更高效
SDIFF set2 set1 # 较低效
七、总结
Redis Set是一个功能强大且高效的数据结构,特别适合处理需要去重和集合运算的场景:
核心优势:
- 高效去重:自动保证元素唯一性
- 快速查询:O(1)时间复杂度的存在性检查
- 丰富运算:支持交、并、差集计算
- 内存优化:内部实现IntSet或HashTable编码
适用场景总结:
- ✅ 用户标签系统:基于兴趣的推荐和分组
- ✅ 社交关系:共同好友、好友推荐
- ✅ 统计分析:UV统计、独立计数
- ✅ 内容筛选:多条件过滤、去重
- ✅ 随机选择:抽奖、随机推荐
- ✅ 访问控制:黑白名单管理
最佳实践建议:
- 避免大集合:使用SSCAN代替SMEMBERS
- 合理使用编码:尽量使用整数和小集合以启用IntSet
- 优化集合运算:小集合在前,使用*STORE命令
- 及时清理数据:设置合理的过期时间
- 监控性能:定期检查大Key和慢查询
掌握Redis Set的特性和应用场景,能够在实际项目中构建更高效、更灵活的数据处理系统。无论是社交应用、电商系统还是数据分析平台,Set都能发挥重要作用。