引言
Redis的Set(集合)类型是一个无序的、元素不重复的数据结构,它基于哈希表实现,类似于C++中的unordered_set。Set类型在Redis中提供了高效的集合运算能力,特别适合处理需要去重、求交并差集的场景。本篇博客将深入探讨Redis Set的底层原理、核心命令和多种实用场景。
一、Redis Set核心特性
1.1 基本特性
1.2 与List的对比
| 特性 | Set | List |
|---|---|---|
| 顺序性 | 无序 | 有序(插入顺序) |
| 重复性 | 不允许重复 | 允许重复 |
| 访问方式 | 不支持下标访问 | 支持下标访问 |
| 底层实现 | 哈希表、intset | QuickList |
| 主要用途 | 去重、集合运算 | 队列、栈、时间线 |
二、Redis Set核心命令详解
2.1 基本操作命令
SADD - 添加元素
# 语法 SADD key member [member ...] # 示例 SADD tags:user:1001 "tech" "music" "sports" # 返回:3(成功添加的元素个数) # 添加重复元素 SADD tags:user:1001 "tech" # 返回:0(元素已存在)SMEMBERS - 获取所有元素
# 语法 SMEMBERS key # 示例 SMEMBERS tags:user:1001 # 可能返回:"tech", "music", "sports"(顺序不确定) # ⚠️ 警告:时间复杂度O(N),大集合需谨慎使用SISMEMBER - 检查元素存在
# 语法 SISMEMBER key member # 示例 SISMEMBER tags:user:1001 "tech" # 返回:1(存在) SISMEMBER tags:user:1001 "art" # 返回:0(不存在)SCARD - 获取元素个数
# 语法 SCARD key # 示例 SADD tags:user:1001 "tech" "music" "sports" SCARD tags:user:1001 # 返回:3 # ⚠️ 时间复杂度:O(1)2.2 删除与移动操作
SPOP - 随机删除并返回
# 语法 SPOP key [count] # 示例 SADD lottery "user1" "user2" "user3" "user4" "user5" SPOP lottery 2 # 随机删除并返回2个元素 # 可能返回:"user3", "user1"SREM - 删除指定元素
# 语法 SREM key member [member ...] # 示例 SADD tags:user:1001 "tech" "music" "sports" "art" SREM tags:user:1001 "art" "music" # 删除两个元素 # 返回:2(成功删除的个数)SMOVE - 移动元素
# 语法 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 - 随机获取元素
# 语法 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 交集运算
SINTER - 计算交集
# 语法 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 - 计算并存储交集
# 语法 SINTERSTORE destination key [key ...] # 示例:存储共同好友 SINTERSTORE common:user1:user2 friends:user1 friends:user2 # 返回:1(交集元素个数) SMEMBERS common:user1:user2 # 返回:"user3"3.2 并集运算
SUNION - 计算并集
# 语法 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 - 计算并存储并集
# 语法 SUNIONSTORE destination key [key ...] # 示例:创建标签池 SUNIONSTORE all:tags tags:article1 tags:article2 SCARD all:tags # 返回:53.3 差集运算
SDIFF - 计算差集
# 语法 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 - 计算并存储差集
# 语法 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(整数集合)
# 当集合满足以下条件时使用IntSet: # 1. 所有元素都是整数 # 2. 元素数量较少(默认512个) # 3. 整数在64位有符号范围内 # 示例:使用IntSet SADD small:numbers 1 2 3 4 5 OBJECT ENCODING small:numbers # 返回:"intset"HashTable(哈希表)
# 当不满足IntSet条件时使用HashTable # 示例:使用HashTable SADD large:set "string1" "string2" 100 # 包含非整数 OBJECT ENCODING large:set # 返回:"hashtable"4.2 内存优化配置
# Redis配置文件中Set相关参数 set-max-intset-entries 512 # IntSet最大元素数量 # 当元素数量超过此值或包含非整数时,转换为HashTable五、Redis Set应用场景
5.1 用户标签系统
实现代码:
# 为用户添加标签 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:tags5.2 共同好友系统
# 存储好友关系 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:10025.3 UV统计(独立访客)
# 每日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 大集合处理策略
# 问题: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 集合运算性能优化
# 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都能发挥重要作用。