推荐使用 redis spop 实现抽奖,通过 set 存储奖品 id 保证去重,配合库存校验、原子性操作与限频防刷,闭环处理权重、并发、记录等关键逻辑。

ThinkPHP 做抽奖活动,用 Redis 的 SET 存奖品池、配合 SPOP 或 SRANDMEMBER 随机取奖,是高效又防重复的常见方案。关键不在“怎么写代码”,而在于“怎么设计逻辑”——奖品权重、库存校验、并发安全、中奖记录都要闭环。
用 Redis SET 存奖品 ID,保证不重复抽取
把所有可抽中的奖品 ID(比如 prize:1001、prize:2005)一次性塞进一个 Redis SET,例如键名为 lottery:pool:202410:
- 每次抽奖前先
SCARD lottery:pool:202410查剩余数量,为 0 就直接返回“已抽完” - 用
SRANDMEMBER lottery:pool:202410 1随机取 1 个 ID(不删除),适合“可重复参与但单次只中一奖”的场景 - 用
SPOP lottery:pool:202410 1随机取并移除,适合“每个奖品仅限一人获得”的硬性去重(推荐)
支持权重的方案:不用 SET,改用 Redis ZSET + 权重分值
如果一等奖概率 1%、安慰奖 90%,纯 SET 无法控制概率。这时改用 ZSET,把奖品 ID 当 member,权重当 score:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 比如添加:
ZADD lottery:weighted:202410 10 "prize:1001" 900 "prize:3002"(总分 1000,对应百分比) - 抽奖时生成 1~1000 的随机整数
$rand,再用ZRANGEBYSCORE lottery:weighted:202410 $rand $rand LIMIT 0 1精准命中 - 取到后立刻
ZREM lottery:weighted:202410 prize:1001,避免重复中同一高权重大奖
ThinkPHP 中整合 Redis 抽奖的关键操作
在控制器里写抽奖方法,注意三步不能少:查库存 → 取奖 → 写记录。示例(基于 TP6 + Redis 扩展):
- 用
Cache::store('redis')->handler()获取原生 Redis 实例(避免 ThinkPHP 缓存封装限制) - 用
pipeline()包裹SPOP和后续HSET中奖用户信息,保证原子性 - 中奖后立即写 MySQL 记录(含用户ID、奖品ID、时间、IP),同时用
INCR lottery:total:202410统计总参与人数 - 失败时记得
rollback并记录日志,比如 Redis 连接超时或SPOP返回空
防刷和体验优化细节
真实活动中,光抽得准不够,还要防机器刷、保用户体验:
- 用户维度限频:用
SETNX user:12345:lottery:202410 1 EX 86400限制每人每天最多 1 次 - 前端按钮点击后置灰 + 倒计时,防止连点触发多次请求
- 中奖结果异步通知:Redis 发布
lottery:win:12345,后台监听消费,发短信/站内信 - 奖品池动态更新?用
EXPIRE lottery:pool:202410 3600设过期,配合定时任务重载新奖品列表
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










