如何在Redis中使用Set数据类型实现亿级用户抽奖系统

云强小哥_2343

云强小哥_2343

2026-09-10

972人浏览

原创

不能直接用sadd+srandmember做亿级抽奖,因srandmember在亿级set上时间复杂度近o(n)、不保证去重、无状态记录且易阻塞主线程;应采用分片set+sscan+lua原子脚本+异步落库方案。

如何在redis中使用set数据类型实现亿级用户抽奖系统

为什么不能直接用 SADD + SRANDMEMBER 做亿级抽奖

直接用 SRANDMEMBER users 1000 抽一万人,在亿级 Set 上会阻塞 Redis 主线程,且结果不可控:重复抽中、无法去重校验、无中奖状态记录。更关键的是,SRANDMEMBER 底层是随机采样,当集合过大时,实际时间复杂度接近 O(N),不是常数时间。

真实场景要求:每个用户仅中一次、支持分批次开奖、可追溯中奖记录、不影响在线写入吞吐。

  • 亿级用户数据不能全塞进一个 Set —— 内存占用高,单 key 过大易触发 Redis 阻塞或主从同步延迟
  • SRANDMEMBER 不保证不重复(除非加 count 参数且远小于集合大小),但抽奖必须严格去重
  • 中奖后要立刻标记状态,否则并发请求可能重复中奖

用分片 Set + SSCAN 渐进式遍历替代全量随机

把亿级用户按 ID 哈希分片到多个 Set,例如 lottery:users:0 ~ lottery:users:999,每片约百万级。这样单个 Set 大小可控,SSCAN 可分批迭代,避免阻塞。

抽奖逻辑不再依赖“一次性随机”,而是:遍历每个分片 → 对每个分片用 SRANDMEMBER 尝试抽若干个 → 检查是否已中奖(查另一个 SetHash)→ 成功则 SREM + SADD lottery:winners + 记录详情。

  • 分片数建议取 100~1000,需权衡 SSCAN 的游标管理复杂度和单 Set 大小
  • 务必用 SSCAN 而非 SMEMBERS,后者会一次性加载全部成员,内存爆炸
  • 每次 SSCAN 设置 COUNT 为 1000~5000,太小遍历轮次多,太大单次耗时高
  • 遍历过程中需用 Lua 脚本保证 检查-中奖-移除 原子性,否则并发下仍可能重复中奖

用 Lua 脚本封装原子中奖逻辑,避免竞态

核心动作——“检查用户是否未中奖、若未中则标记并移出待抽池”——必须原子执行。Redis 单线程特性下,Lua 是唯一可靠手段。

Redis Skill - 高性能缓存管理
Redis Skill - 高性能缓存管理

Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。

下载
local uid = KEYS[1]
local pool_key = KEYS[2]
local winner_key = KEYS[3]
if redis.call('SISMEMBER', winner_key, uid) == 0 then
  redis.call('SREM', pool_key, uid)
  redis.call('SADD', winner_key, uid)
  return 1
else
  return 0
end

调用时传入用户 ID、当前分片 key、中奖池 key。返回 1 表示成功中奖,0 表示已中过或不在池中。

  • 不要在客户端做“先查再删”——网络延迟 + 多客户端并发必然导致重复中奖
  • 脚本里禁止使用 redis.call('SRANDMEMBER', ...),因为 SRANDMEMBER 在 Lua 中不保证幂等,且无法控制是否已中奖
  • 该脚本只处理单个用户,批量中奖需由外层循环调用,配合 SSCAN 游标推进

中奖结果落库与一致性兜底怎么做

Redis 只做高速筛选,最终中奖名单必须落地 MySQL 或 TiDB。但不能等全部抽完再写库——失败重试成本高、无法实时展示。

推荐做法:每成功中奖 100 人,就异步批量写入数据库,并记录当前分片游标位置。若进程崩溃,可从最后游标恢复,避免漏抽或重抽。

  • Redis 中的 lottery:winners Set 仅作临时缓存,不作为唯一事实源;它可能因故障丢失,但数据库有完整记录
  • 分片游标必须持久化(如写入另一个 Redis key 或 DB),否则重启后无法续抽
  • 对已中奖用户,除了 SADD lottery:winners,建议额外用 HSET lottery:detail:<uid> prize_id 123 draw_time 1717023456</uid> 存明细,方便查单个用户
  • 别忽略超时控制:整个抽奖任务设总超时(如 30 分钟),防止某一分片卡死拖垮全局

真正难的不是抽,是让每一次中奖都可验证、可回溯、可中断续抽。分片、游标、Lua、异步落库这四点缺一不可,少一个就会在千万级并发下暴露问题。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

redis redis缓存 redis集群

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.31

2311

5

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

2025.10.31

494

9

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

2026.02.12

402

19

常用的数据库软件
常用的数据库软件

常用的数据库软件有MySQL、Oracle、SQL Server、PostgreSQL、MongoDB、Redis、Cassandra、Hadoop、Spark和Amazon DynamoDB。更多关于数据库软件的内容详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.02

3989

19

内存数据库有哪些
内存数据库有哪些

内存数据库有Redis、Memcached、Apache Ignite、VoltDB、TimesTen、H2 Database、Aerospike、Oracle TimesTen In-Memory Database、SAP HANA和ache Cassandra。更多关于内存数据库相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.14

3495

11

mongodb和redis哪个读取速度快
mongodb和redis哪个读取速度快

redis 的读取速度比 mongodb 更快。原因包括:1. redis 使用简单的键值存储,而 mongodb 存储 json 格式的数据,需要解析和反序列化。2. redis 使用哈希表快速查找数据,而 mongodb 使用 b-tree 索引。因此,redis 在需要高性能读取操作的应用程序中是一个更好的选择。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.02

6152

6

redis怎么做缓存服务器
redis怎么做缓存服务器

redis 作为缓存服务器的答案:redis 是一款开源、高性能、分布式的键值存储,可作为缓存服务器使用。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

583

6

redis怎么解决数据一致性
redis怎么解决数据一致性

redis 提供了两种一致性模型,以维护副本数据一致性:强一致性 (sync) 确保写操作仅在复制到所有从节点后才完成;最终一致性 (async) 则在主节点上写操作后认为已完成,牺牲一致性换取性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

696

6

mysql和redis怎么保证双写一致性
mysql和redis怎么保证双写一致性

确保 mysql 和 redis 双写一致性的技术包括:1、事务性更新:同时更新 mysql 和 redis,保证一致性;2、主从复制:mysql 主服务器更改同步到 redis 从服务器;3、基于事件的更新:mysql 记录更改并发送到 redis等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.07

5522

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
phpEnv手册
phpEnv手册

共0课时 | 0人学习

进程与SOCKET
进程与SOCKET

共6课时 | 0.5万人学习