当前位置: 首页 > news >正文

从 10 万人里随机抽 10 个,怎么做最快?


案发场景:
公司年会,全员 5 万人。老板要在台上抽 10 个特等奖(每人一辆特斯拉)。
你的初级后端同事写了这样一段逻辑:把 5 万人从数据库捞到 Java 的List里,然后用Collections.shuffle(list)打乱,再取前 10 个。
灾难降临:
老板按下抽奖键的那一刻,网关超时,Java 堆内存剧烈抖动,GC 狂飙,大屏幕转圈圈卡死。老板的脸黑了。
破局之道:
把这 5 万人的工号提前扔进 Redis 的Set集合里。
抽奖?只需一行代码:SPOP users:2026 10
耗时0.001 秒。不仅快,而且保证绝对不重复中奖。这就是底层的力量。


1. 核心原理解剖:为什么它能做到 O(1)?

在 MySQL 中,ORDER BY RAND()之所以慢,是因为它需要为表中的每一行生成一个随机数,然后把所有行放进一个临时表里,再对这个临时表进行全量排序(Sort)。数据越多,死得越快。

而在 Redis 中,Set集合的底层实现是哈希表(Hash Table)(当元素都是极小的整数时,是 IntSet,但抽奖场景通常是字符串 ID,所以退化为哈希表)。

Redis 的随机魔法:
Redis 不需要对全量数据排序!它直接在哈希表的数组大小范围内,生成一个随机索引(Index)。

  1. 随机定位到一个哈希槽(Bucket)。
  2. 如果槽里有元素,直接返回!
  3. 如果槽是空的,或者遇到哈希冲突的链表,按照一定的随机算法顺藤摸瓜即可。
    这就好比从一个装满球的黑布袋里,闭着眼睛随便抓一个,根本不需要把袋子里的球先排个序。所以它的时间复杂度是惊人的O(1)

2. 抽奖双雄:SRANDMEMBERvsSPOP

虽然都是随机抽,但这两个命令有着本质的区别,决定了它们完全不同的业务场景。

武将一:SPOP(连拿带跑) —— 破坏性抽取
  • 语法:SPOP key [count]
  • 特性:随机弹出一个或多个元素,并从 Set 中彻底删除它们。
  • 暗语:“拿了我的给我吐出来”。一旦被抽中,这个元素就永远离开了奖池。天然防重复中奖!
武将二:SRANDMEMBER(只看不拿) —— 非破坏性抽取
  • 语法:SRANDMEMBER key [count]
  • 特性:随机返回一个或多个元素,但保留在 Set 中不删除
  • 深水区魔法(极其重要):它的count参数大有玄机!
  • count > 0: 返回的元素绝对不重复。最多返回整个集合的数量。
  • count < 0: 返回的元素可能重复!它会执行一种带有放回的随机抽样(抽取后扔回奖池再抽)。返回的数量可以大于整个集合的数量。

3. 三大高频实战场景

场景一:绝对不重复的“年会抽奖 / 抢红包” (SPOP)
  • 痛点:100 个人抢 10 个红包,或者年会抽 10 个手机。同一个人绝对不能中奖两次。
  • 方案:预热时,把 100 人的 IDSADD进奖池pool:annual_meeting
    老板点击抽奖:
# 直接弹出 10 个幸运儿,且他们自动从奖池消失,下一轮抽奖绝不会再有他们SPOP pool:annual_meeting10
场景二:题库盲抽 / 首页商品推荐 (SRANDMEMBER+ 正数)
  • 痛点:题库里有 10000 道考题,每次用户进入页面,随机给他推荐 5 道不一样的题,但考题不能从题库里删掉(别人还要考)。
  • 方案:
# 随机选 5 个不重复的题目,题库安然无恙SRANDMEMBER questions:bank5
场景三:带有权重的“每日大转盘” (SRANDMEMBER+ 负数)
  • 痛点:大转盘有 6 个格子:“谢谢参与”、“10积分”、“100积分”、“iPhone”(极低概率)。
  • 极客方案(空间换逻辑):我们不需要写复杂的权重算法。可以直接按比例向 Set 里塞入代币。比如塞入 10000 个“谢谢参与”,100 个“10积分”,1 个“iPhone”。
    用户点击抽奖:
# count为负数:不仅保留奖池,而且允许重复抽取!# 比如一次性抽 3 连抽,可能抽到 3 个“谢谢参与”SRANDMEMBER lucky_wheel:2026-3

4. 代码落地:Spring Boot 实战演示

在 Java 开发中,通过StringRedisTemplate调用非常简单优雅:

importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.List;importjava.util.Set;@ServicepublicclassLotteryService{privatefinalStringRedisTemplateredisTemplate;publicLotteryService(StringRedisTemplateredisTemplate){this.redisTemplate=redisTemplate;}/** * 场景一:年会抽奖 (抽完就没,不重复中奖) */publicList<String>popAnnualWinners(StringpoolKey,intcount){// SPOP 对应 pop()returnredisTemplate.opsForSet().pop(poolKey,count);}/** * 场景二:首页随机推荐 5 个商品 (只看不拿,单次不重复) */publicSet<String>getRandomProducts(StringpoolKey,intcount){// SRANDMEMBER 正数 对应 distinctRandomMembers()returnredisTemplate.opsForSet().distinctRandomMembers(poolKey,count);}/** * 场景三:大转盘 10 连抽 (只看不拿,单次可能重复抽到同一个普通奖品) */publicList<String>wheelTenDraws(StringpoolKey){// SRANDMEMBER 负数 对应 randomMembers()returnredisTemplate.opsForSet().randomMembers(poolKey,10);}}

5. 避坑指南:它真的是“绝对随机”吗?

作为一个有追求的架构师,你必须知道 Redis 随机抽样的底牌。

严格意义上说,Redis 的SRANDMEMBER并不是密码学意义上的绝对公平随机
当 Set 的底层是哈希表时,如果哈希表发生了扩容(Rehash),导致某些槽(Bucket)里的链表特别长,而某些槽比较空。Redis 随机选槽时,落在元素密集槽里的元素,被抽中的概率会微微低于那些独占一个槽的元素。

但是(划重点):这个概率偏差极小,对于日常的营销抽奖、内容推荐、甚至普通的盲盒玩法,其公平性已经完全足够,绝对可以放心使用。除非你在做涉及到巨额资金博彩的绝对随机数生成,才需要去自己手写梅森旋转算法或请求硬件随机数发生器。


总结

千万不要在关系型数据库里做随机排序,这是写进架构师避坑指南第一页的铁律。

拥抱 Redis 的Set吧!无论是用SPOP霸道地抽走大奖,还是用SRANDMEMBER优雅地翻牌子,时间复杂度 O(1) 的降维打击,能让你的抽奖系统在老板按下按钮的瞬间,丝滑得像德芙巧克力一样。

http://www.jsqmd.com/news/450375/

相关文章:

  • 基于Java+SSM+Flask政府项目管理平台(源码+LW+调试文档+讲解等)/政府项目/管理平台/项目管理软件/政务管理/公共项目/项目监管/项目管理工具/项目追踪/项目控制系统/政府工程
  • C语言:2026.3.8
  • springboot与springcloud对应版本
  • 联合省选2026游记 | 跟我学一辈子OI
  • 基于Java+SSM+Flask个人消费管理系统(源码+LW+调试文档+讲解等)/个人财务管理/消费记录软件/个人支出管理系统/消费追踪工具/个人消费分析工具/理财软件/消费管理软件/个人账目管理系统
  • AI时代人人都是产品经理:避坑指南:AI 时代做产品,90% 的人都会踩的 3 个认知误区
  • SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
  • springboot中@PostConstruct注解使用详解
  • 2026年宁夏抖音短视频代运营服务商5强推荐名单公布 - 精选优质企业推荐榜
  • 智慧乡村管理系统项目。将 DeepSeek 大模型 接入传统的 Spring Boot + Vue 业务系统,实现了从“信息化管理”到“智能化服务”
  • MATLAB Simulink 卷积码
  • 鸿蒙应用开发工程师:技术深度与职业发展全景解析
  • 2026年苏州抖音短视频代运营服务商5强推荐名单公布 - 精选优质企业推荐榜
  • 第三部分 — 服务工作者(后台)服务工作者生命周期及注意事项(从 MV2 迁移到 MV3)
  • Python之TypeVar深入解析
  • 一文搞懂:缓存三大问题(击穿、穿透、雪崩)原理及全套解决方案
  • 你的电视 2.3.8 | 空壳直播软件,支持多个线路,附直播源
  • SpringBoot实战:高效实现API限流策略
  • 基于Java+SSM+Flask疫情防控管理系统(源码+LW+调试文档+讲解等)/疫情防控/管理系统/防疫管控/公共卫生/健康管理/疫情监测/疾病控制/病毒防范/流行病学/疫情报告/健康监测/疫区管理
  • 2026年河北抖音短视频代运营5强推荐榜单发布 - 精选优质企业推荐榜
  • AI 数学的秘密花园:09.多头注意力是什么?(一群专家分工合作,竞争又抱团)
  • 复杂 Agent 系统的 10 个核心设计模式(源码级)
  • Harmonyos应用实例十七:找规律——图形与数列规律
  • Swoole的利弊的核心概念的庖丁解牛
  • nodejs vue3农产品网上商城系统 半亩菜园线上预售系统
  • Dify 助力企业级 AI Agents 开发:2026 最新真实案例深度解析与实战指南
  • C#上位机PLC通信全栈实战:西门子/三菱/欧姆龙/汇川全品牌通用框架,一次开发终身复用
  • HarmonyOS APP开发:从理论到实践
  • 【2026年最新600套毕设项目分享】基于BS的企业财务管理信息系统(14071)
  • 每天了解几个MCP SERVER:让 AI 能够获取股票、加密货币等市场数据Alpaca