Redis 集合(Set)完全攻略
“无序 + 自动去重” —— 标签系统、抽奖池、共同好友、权限控制… 去重神器!
一、Set 核心特点
| 特性 | 说明 |
|---|---|
| 底层结构 | intset(小整数集合) 或 hashtable(通用) |
| 无序 | 不保证插入顺序 |
| 自动去重 | 同一元素只存一次 |
| 最大成员数 | 2^32 – 1(约 42 亿) |
| 核心操作 O(1) | SADD / SREM / SISMEMBER |
| 集合运算 | 交集、并集、差集(支持多 key) |
| 内部编码 | 小整数 → intset,其他 → hashtable |
二、Set 命令全表(共 18 个)
| 命令 | 说明 | 示例 |
|---|---|---|
SADD key m1 m2 | 添加成员 | SADD tags "redis" "cache" |
SREM key m1 | 删除成员 | SREM tags "old" |
SMEMBERS key | 获取全部成员 | SMEMBERS tags |
SCARD key | 成员数量 | SCARD tags |
SISMEMBER key m | 是否存在 | SISMEMBER tags "redis" |
SRANDMEMBER key [count] | 随机返回(不删除) | SRANDMEMBER users 3 |
SPOP key [count] | 随机弹出(删除) | SPOP lottery 1 |
SMOVE src dest m | 原子移动 | SMOVE set:a set:b "x" |
SINTER k1 k2 | 交集 | SINTER friends:1 friends:2 |
SUNION k1 k2 | 并集 | SUNION set:a set:b |
SDIFF k1 k2 | 差集 | SDIFF followers:1 followings:1 |
SINTERSTORE dest k1 k2 | 交集存入新 key | SINTERSTORE common:ab a b |
SUNIONSTORE dest k1 k2 | 并集存入新 key | SUNIONSTORE all:ab a b |
SDIFFSTORE dest k1 k2 | 差集存入新 key | SDIFFSTORE diff:ab a b |
SSCAN key cursor [MATCH p] | 渐进式遍历 | SSCAN tags 0 MATCH r* |
三、核心实战场景
1. 标签系统(用户/文章标签)
# 给用户添加标签
SADD user:1001:tags "golang" "redis" "docker"
# 查询用户标签
SMEMBERS user:1001:tags
# 删除标签
SREM user:1001:tags "docker"
2. 抽奖系统(公平随机)
# 奖池
SADD lottery:2025 "u1" "u2" "u3" "u4" "u5"
# 随机抽 3 人(不重复)
SRANDMEMBER lottery:2025 3
# 或者弹出中奖者(移除)
SPOP lottery:2025 1
3. 共同好友 / 推荐系统
# 用户 A 的好友
SADD friends:1001 "u2" "u3" "u4" "u5"
# 用户 B 的好友
SADD friends:1002 "u3" "u4" "u6"
# 共同好友
SINTER friends:1001 friends:1002
# → "u3" "u4"
4. 权限控制(角色 → 权限集合)
SADD role:admin "user:delete" "post:edit" "system:restart"
SADD role:user "post:view" "comment:add"
# 检查权限
SISMEMBER role:admin "user:delete" → 1
5. 去重统计(UV、IP 去重)
# 每天访问用户去重
SADD uv:20251112 "u1" "u2" "u1" "u3"
SCARD uv:20251112 → 3
# 保留7天
EXPIRE uv:20251112 604800
更省内存?用
HyperLogLog
四、Set vs List vs Hash 对比
| 场景 | 推荐 |
|---|---|
| 需要去重 | Set |
| 保持顺序 | List |
| 字段-值映射 | Hash |
| 频繁中间插入 | List |
| 集合运算 | Set |
五、内部编码优化(intset vs hashtable)
# 小整数集合 → intset(超省内存)
SADD nums 1 2 3 4 5
OBJECT ENCODING nums → "intset"
# 含字符串 → hashtable
SADD mixed "a" "b" 1
OBJECT ENCODING mixed → "hashtable"
切换阈值(配置文件)
| 配置 | 默认值 | 说明 |
|---|---|---|
set-max-intset-entries | 512 | 成员 > 512 或非整数 → hashtable |
生产建议:保持默认
六、性能与内存优化建议
| 建议 | 说明 |
|---|---|
| 避免大 Set | > 100万成员 → 拆分或用 HyperLogLog |
用 SSCAN 替代 SMEMBERS | 大集合遍历 |
| 集合运算前估算大小 | 避免 O(N) 卡顿 |
| 设置 TTL | 防止内存泄漏 |
| Pipeline 批量操作 | 减少 RTT |
# 安全遍历大 Set
SSCAN myset 0 MATCH "user:*" COUNT 100
七、大 Set 排查与拆分
排查大 Set
redis-cli --bigkeys
# Biggest set found "online:users" with 5000000 members
拆分方案
# 原:SADD online:users "u1" "u2" ...
# 拆:按地区/时间分片
SADD online:users:beijing "u1"
SADD online:users:shanghai "u2"
八、一键速查表
# 基础
SADD s a b c
SREM s c
SMEMBERS s
SCARD s
SISMEMBER s a
# 随机
SRANDMEMBER s 2
SPOP s 1
# 集合运算
SINTER a b
SUNION a b
SDIFF a b
# 存储结果
SINTERSTORE common a b
# 遍历
SSCAN s 0 MATCH r*
九、客户端代码示例
Python (redis-py)
import redis
r = redis.Redis()
# 标签系统
r.sadd('user:1001:tags', 'python', 'redis', 'ai')
r.srem('user:1001:tags', 'ai')
tags = r.smembers('user:1001:tags')
print(tags) # {b'python', b'redis'}
# 共同好友
common = r.sinter('friends:1001', 'friends:1002')
print(common)
# 抽奖
winner = r.spop('lottery:2025')
Go (go-redis)
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
rdb.SAdd(ctx, "tags", "golang", "redis")
exists, _ := rdb.SIsMember(ctx, "tags", "redis").Result()
十、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 内存暴涨 | Set 无限增长 | 加 TTL 或分片 |
SMEMBERS 卡顿 | 成员 > 100万 | 改用 SSCAN |
| 集合运算慢 | 输入集合太大 | 预估大小,拆分 |
| 去重不生效 | 使用 List | 改用 Set |
十一、Set 在高并发中的妙用
1. 在线用户去重
# 用户上线
SADD online:users "u:1001"
EXPIRE online:users 300 # 5分钟心跳
# 用户下线
SREM online:users "u:1001"
# 当前在线人数
SCARD online:users
2. 黑名单系统
SADD blacklist:ip "1.2.3.4" "5.6.7.8"
SISMEMBER blacklist:ip "1.2.3.4" → 1 → 拒绝访问
完成!你已精通 Redis Set!
# 一行命令体验 Set 全功能
redis-cli <<EOF
SADD s a b c a
SMEMBERS s
SCARD s
SRANDMEMBER s 2
SPOP s
SINTER s s2
SSCAN s 0
EOF
下一步推荐:
需要我送你:
- “百万级抽奖系统(Set + Lua 防刷)”?
- “社交共同好友推荐引擎”?
- “动态标签系统(Set + Hash)”?
回复:抽奖 | 共同好友 | 标签系统 即可!