Redis的Set:你以为我是青铜?其实我是百变星君!

大家好,我是你们的老朋友Redis,今天要给大家讲讲我的秘密武器------Set数据结构。听说有人觉得我就是个青铜选手,只能存存标签、记记好友?今天就让你们见识见识,什么叫"集美貌与才华于一身"的百变星君!


一、Set的基因检测报告(底层编码)

当你们用SADD命令往我身体里塞元素时,我体内会发生奇妙的基因变异。偷偷告诉你们,我有双重人格:

  • intset(整数集合) :当所有元素都是整数且元素数量 ≤512(默认)时,我就是这个精打细算的抠门管家。像这样整整齐齐排排坐:
arduino 复制代码
typedef struct intset {
    uint32_t encoding;  // 比如INTSET_ENC_INT16
    uint32_t length;    // 元素个数
    int8_t contents[];  // 元素数组
} intset;
  • hashtable(哈希表) :当遇到非整数元素,或者元素数量超过阈值,我就秒变土豪,直接甩出哈希表大杀器。这时候每个元素都是哈希表里的key,value统一指向NULL(就是这么任性)。

举个栗子:你往集合里加数字就像往钱包里塞钢镚,用intset省空间;突然要加个"老板的私房钱"这种字符串,钱包瞬间爆炸成保险柜!


二、Set的七十二变(底层数据结构)

1. 整型特工队(intset)

这个编码暗藏玄机:它会根据最大元素自动升级编码方式!比如原本用16位存储数字,突然来了个32768(超过int16),整个集合会立即升级到int32,像变形金刚合体一样酷炫。

ruby 复制代码
127.0.0.1:6379> SADD my_set 1 2 3
(integer) 3
127.0.0.1:6379> OBJECT ENCODING my_set
"intset"
127.0.0.1:6379> SADD my_set 32768
(integer) 1
127.0.0.1:6379> OBJECT ENCODING my_set 
"hashtable"

2. 哈希表狂战士(hashtable)

当元素放飞自我开始乱来时,哈希表就带着O(1)时间复杂度闪亮登场。这时候每个元素都对应哈希表里的一个entry,查找速度堪比闪电侠。


三、Set的职场生存指南(使用场景)

场景1:百万级标签系统(hashtable的高光时刻)

假设你要给10万用户打标签,每个用户有100个标签。用SINTER查共同标签,速度堪比高铁,时间复杂度O(N*M)?不存在的!Redis用了魔法般的算法优化。

bash 复制代码
# 查找喜欢"火锅"和"密室逃脱"的用户
SINTER users:火锅 users:密室逃脱

场景2:实时布隆过滤器(intset的逆袭)

当处理连续数字时,intset的紧凑内存布局堪称内存刺客。比如做ID去重:

yaml 复制代码
# 存储1-10000的连续ID(仅占16KB)
SADD user_ids 1 2 3 ... 10000
# 判断9999是否存在
SISMEMBER user_ids 9999

场景3:社交裂变大师(集合运算)

想要搞"共同好友""你可能认识的人"?Set的并/交/差集操作就是你的瑞士军刀:

bash 复制代码
# 张三的好友 ∩ 李四的好友 = 共同好友
SINTER friends:张三 friends:李四
# 推荐系统:李四好友 - 张三好友 - 张三 = 潜在好友
SDIFF friends:李四 friends:张三 张三

彩蛋场景:老板的紧急需求

"小王啊,咱们要搞个实时在线用户统计,还要能随机踢人!"

"好的老板,我这就用SADD记录登录用户,SPOP随机踢人,SCARD统计在线人数!"


四、Set的灵魂拷问(思考题)

思考题1:intset遇到字符串就要全体叛变?

Q :为什么不能像渣男一样,留几个整数在intset里,只把字符串转成hashtable?
A:这就好比你在用Excel表格记账:

  1. 当你所有数据都是数字时,Excel可以用紧凑的数值格式(intset模式)
  2. 突然混入"老板的私房钱"这样的文本,整个列都会被强制转为文本格式(hashtable模式)

技术真相

  • intset是连续内存块存储,类似数组
  • 插入不同类型元素会导致内存布局崩溃(想象往整型数组里突然插入字符串指针)
  • Redis选择简单粗暴但可靠的方式:要么全数字,要么全对象

灵魂拷问:如果你非要混搭,可以用两个Set(数字Set+字符串Set),但你会收获双倍的快乐(内存开销)!


思考题2:1亿个IP用Set存?老板会杀了你!

Q :假设每个IP占15字节(如"192.168.1.1")
计算器警告

scss 复制代码
哈希表存储成本 ≈ 1亿 * (指针大小8B + Redis对象头16B + IP值15B) ≈ 3.9GB

而用HyperLogLog

复制代码
12KB就能统计去重数量(但无法存储具体IP)

求生指南

✅ 精确去重且需要具体数据 → 用Set但必须分片(每个Set<1万元素)

✅ 允许误差的去重统计 → HyperLogLog

✅ 既要精确又要省内存 → 上Bloom Filter(但Redis需要插件)

血泪教训:曾有人用Set存用户ID,直到服务器内存爆炸才明白------有些爱(数据)注定不能强求!


思考题3:SMEMBERS vs SSCAN,选错会死!

翻车现场

bash 复制代码
# 当Set有1000万元素时
SMEMBERS huge_set # 客户端卡死,Redis内存飙升
SSCAN huge_set 0 COUNT 100 # 每次温柔地取100个

原理暴击

  • SMEMBERS:一次性打包所有元素(O(n)时间复杂度+O(n)网络传输)
  • SSCAN:游标分页获取(O(1)单次操作+可控的数据量)

使用哲学

  • 小数据 → 随便浪(SMEMBERS真香)
  • 大数据 → 要矜持(SSCAN才是真爱)
  • 需要判断存在性 → SISMEMBER永远安全

思考题4:用Set实现分布式锁?骚操作预警!

常规做法(String类型):

bash 复制代码
SET lock_key uuid NX EX 30 # 专业锁匠

邪道玩法(Set类型):

bash 复制代码
SADD global_lock unique_client_id # 利用Set元素唯一性
EXPIRE global_lock 30 # 记得设过期时间!
DEL global_lock # 释放锁时删除自己ID

致命缺陷

  1. 无法续期(像不能充值的停车卡)
  2. 非原子操作(设过期时间可能失败)
  3. 只能实现排他锁(无法升级为读写锁)

生存建议

  • 玩玩可以,生产环境请用Redlock
  • 非要较真的话,建议在辞职信里写上"我用Set实现了分布式锁"

终极总结表

问题 关键点 保命技巧
intset类型转换 内存连续性决定不能混搭 保持元素类型纯净
海量数据存储 哈希表内存放大效应恐怖 分片存储或换用概率型数据结构
遍历大数据 SMEMBERS是煤气灶,SSCAN是电磁炉 永远对数据量保持敬畏
分布式锁 Set能实现幼儿园级别的锁 别拿玩具枪上战场

五、Set的生存法则(最佳实践)

  • 内存敏感型应用:尽量保持元素为整数,控制数量在512以内(可配置)
  • 高频查询场景:多用SISMEMBER代替SMEMBERS+客户端处理
  • 海量数据场景:考虑分片(每个Set不超过1万元素)
  • 精确去重场景:配合HyperLogLog使用效果更佳(但要注意特性差异)

六、最后一道送命题

假设你要设计一个抽奖系统:

  • 总参与人数:50万
  • 每天开奖10次
  • 要防止重复中奖
  • 要实时显示参与人数

你会怎么用Redis Set设计?欢迎在评论区battle,点赞最高的方案送Redis官方马克杯(才怪)!

相关推荐
kaixin_learn_qt_ing2 分钟前
Golang
开发语言·后端·golang
炒空心菜菜1 小时前
MapReduce 实现 WordCount
java·开发语言·ide·后端·spark·eclipse·mapreduce
独行soc3 小时前
2025年渗透测试面试题总结-阿里云[实习]阿里云安全-安全工程师(题目+回答)
linux·经验分享·安全·阿里云·面试·职场和发展·云计算
wowocpp3 小时前
spring boot Controller 和 RestController 的区别
java·spring boot·后端
后青春期的诗go3 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(二)
开发语言·后端·rust·rocket框架
freellf3 小时前
go语言学习进阶
后端·学习·golang
全栈派森5 小时前
云存储最佳实践
后端·python·程序人生·flask
CircleMouse5 小时前
基于 RedisTemplate 的分页缓存设计
java·开发语言·后端·spring·缓存
獨枭7 小时前
使用 163 邮箱实现 Spring Boot 邮箱验证码登录
java·spring boot·后端
维基框架7 小时前
Spring Boot 封装 MinIO 工具
java·spring boot·后端