Redis Set类型完全指南:无序集合的原理与应用

引言

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 推荐系统 推荐逻辑:

  1. 共同好友越多,推荐权重越高
  2. 二度人脉推荐
  3. 兴趣相似度匹配
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是一个功能强大且高效的数据结构,特别适合处理需要去重和集合运算的场景:

核心优势:

  1. 高效去重:自动保证元素唯一性
  2. 快速查询:O(1)时间复杂度的存在性检查
  3. 丰富运算:支持交、并、差集计算
  4. 内存优化:内部实现IntSet或HashTable编码

适用场景总结:

  • 用户标签系统:基于兴趣的推荐和分组
  • 社交关系:共同好友、好友推荐
  • 统计分析:UV统计、独立计数
  • 内容筛选:多条件过滤、去重
  • 随机选择:抽奖、随机推荐
  • 访问控制:黑白名单管理

最佳实践建议:

  1. 避免大集合:使用SSCAN代替SMEMBERS
  2. 合理使用编码:尽量使用整数和小集合以启用IntSet
  3. 优化集合运算:小集合在前,使用*STORE命令
  4. 及时清理数据:设置合理的过期时间
  5. 监控性能:定期检查大Key和慢查询

掌握Redis Set的特性和应用场景,能够在实际项目中构建更高效、更灵活的数据处理系统。无论是社交应用、电商系统还是数据分析平台,Set都能发挥重要作用。

相关推荐
wang6021252183 小时前
为什么不采用级联删除而选择软删除
数据库·oracle
历程里程碑3 小时前
C++ 9 stack_queue:数据结构的核心奥秘
java·开发语言·数据结构·c++·windows·笔记·算法
醇氧3 小时前
【Windows】从守护到终结:解析一个 Java 服务的优雅停止脚本
java·开发语言·windows
变形侠医4 小时前
比 Kettle 快2倍的 Java ETL 开源库:Etl-engine
数据库
soft20015254 小时前
从一次增删改操作开始:彻底理解 MySQL Buffer Pool 的地位与作用
数据库·mysql
feathered-feathered4 小时前
Redis基础知识+RDB+AOF(面试)
java·数据库·redis·分布式·后端·中间件·面试
whm27774 小时前
Visual Basic 建立数据库
开发语言·数据库·visual studio
檀越剑指大厂5 小时前
【数据库系列】倒排索引(Inverted Index)
数据库·inverted-index
闲人编程5 小时前
权限系统设计与RBAC实现
java·网络·数据库·rbac·权限·codecapsule