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

PHP + Redis 实现抽奖算法(ThinkPHP5)

在使用 ThinkPHP5 和 Redis 实现抽奖算法时,我们可以结合 Redis 的高性能数据结构来管理奖品的库存、用户的抽奖机会,并确保并发情况下的公平性和一致性。下面是一个实现抽奖算法的示例,涵盖了以下步骤:

  1. 设置奖品池:将奖品和奖品数量存储在 Redis 中。
  2. 用户抽奖:根据抽奖概率,从奖品池中抽取奖品。
  3. 中奖处理:扣减奖品库存并记录中奖信息。
  4. 避免重复中奖:确保用户在抽奖期间不会重复抽中相同的奖品。

1. 初始化奖品池及其中奖概率

首先,在初始化奖品池时,为每个奖品设置库存和中奖概率。这里的中奖概率总和可以小于或大于100%。

在 Redis 中创建一个哈希表来存储奖品及其库存。可以在 ThinkPHP 的控制器中初始化奖品池。

use think\facade\Cache;class LotteryController {// 初始化奖品池public function initPrizePool() {$prizes = ['prize1' => ['quantity' => 10, 'probability' => 50],  // 奖品1,库存10个,中奖概率50%'prize2' => ['quantity' => 5,  'probability' => 30],  // 奖品2,库存5个,中奖概率30%'prize3' => ['quantity' => 1,  'probability' => 15],  // 奖品3,库存1个,中奖概率15%'prize4' => ['quantity' => 50, 'probability' => 5]    // 奖品4,库存50个,中奖概率5%];foreach ($prizes as $prize => $data) {Cache::store('redis')->hSet('prize_pool', $prize, $data['quantity']);Cache::store('redis')->hSet('prize_probabilities', $prize, $data['probability']);}return json(['status' => 'Prize pool initialized']);}
}

2. 高并发下用户抽奖逻辑

编写抽奖逻辑,随机抽取奖品,加入 Redis 锁的获取和释放逻辑:

use think\facade\Cache;class LotteryController {// 用户抽奖public function draw() {$userId = session('user_id'); // 获取当前用户ID$lockKey = "lock:draw"; // 锁的键$lockTimeout = 5; // 锁定时间(秒)// 尝试获取锁if (Cache::store('redis')->set($lockKey, $userId, ['nx', 'ex' => $lockTimeout])) {try {// 检查用户是否已经中奖,防止重复抽奖if (Cache::store('redis')->hExists('user_prizes', $userId)) {return json(['status' => 'fail', 'message' => 'You have already won a prize!']);}// 获取奖品池$prizePool = Cache::store('redis')->hGetAll('prize_pool');if (empty($prizePool)) {return json(['status' => 'fail', 'message' => 'No prizes available']);}// 获取奖品的概率列表$prizeProbabilities = Cache::store('redis')->hGetAll('prize_probabilities');// 计算总概率$totalProbability = array_sum($prizeProbabilities);// 生成一个随机数$rand = mt_rand(1, $totalProbability);// 根据随机数选择奖品$cumulativeProbability = 0;$selectedPrize = null;foreach ($prizeProbabilities as $prize => $probability) {$cumulativeProbability += $probability;if ($rand <= $cumulativeProbability) {$selectedPrize = $prize;break;}}// 确认有选中奖品if (!$selectedPrize) {return json(['status' => 'fail', 'message' => 'Sorry, better luck next time']);}// 检查奖品库存并更新$remaining = Cache::store('redis')->hIncrBy('prize_pool', $selectedPrize, -1);if ($remaining < 0) {// 如果库存不足,恢复库存并返回失败信息Cache::store('redis')->hIncrBy('prize_pool', $selectedPrize, 1);return json(['status' => 'fail', 'message' => 'Sorry, all prizes are gone']);}// 记录用户中奖信息Cache::store('redis')->hSet('user_prizes', $userId, $selectedPrize);return json(['status' => 'success', 'message' => 'Congratulations, you won!', 'prize' => $selectedPrize]);} finally {// 释放锁Cache::store('redis')->del($lockKey);}} else {return json(['status' => 'fail', 'message' => 'System busy, please try again later']);}}
}

3. 解释并发处理逻辑

  • 分布式锁:在抽奖逻辑的开始,我们使用 Redis 的 set 命令来获取一个分布式锁,确保当前操作的唯一性。锁的 nx 参数表示“仅在键不存在时设置键”,ex 参数设置锁的超时时间(这里是5秒)。

  • 获取锁成功:如果当前用户成功获取了锁,则继续执行抽奖逻辑。在抽奖结束后,无论成功与否,都需要释放锁。

  • 获取锁失败:如果获取锁失败,说明有另一个用户正在执行抽奖操作,当前用户会收到系统繁忙的提示。

  • 库存处理:在确定用户抽中某个奖品后,使用 Redis 的  hIncrBy  命令减少该奖品的库存。如果库存不足,会回滚该操作并返回失败信息。

4. 调用抽奖

当用户调用 draw() 方法时,系统会根据设定的百分比概率和库存情况随机选择一个奖品。

5. 可扩展性

  • 抽奖概率控制:在实际场景中,你可以通过调整  getRandomPrize()  函数中的逻辑来实现,例如通过给奖品分配比例、权重等。

  • 多用户并发:Redis 的原子操作能够确保在高并发情况下奖品库存的正确性,因此在并发抽奖场景中也可以安全使用。

  • 过期管理:可以为奖品池设置过期时间,避免长期占用内存。用户中奖信息也可以设置过期时间,防止数据堆积。

总结

通过引入 Redis 分布式锁,确保了在高并发情况下抽奖的原子性和安全性。这样可以有效防止多个用户同时抽中同一个奖品导致库存不足的问题,并且提高了系统的稳定性。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 145. 利用 Redis Bitmap实践: 用户签到统计
  • 【ArcGIS Pro原理第一期】各种空间插值原理:GPI、LPI、IDW等
  • 七月刚入职字节跳动的测试开发面试题,附答案
  • 【全网最全】《2024高教社杯/国赛》 C题 思路+代码+文献 蒙特卡洛+遗传算法 一到三问 农作物的种植策略
  • Linux系统运行模式以及链接文件
  • 高级java每日一道面试题-2024年9月04日-前端篇-前端的框架分类有哪些?
  • Google Research 推出高效的Prompt Tuning方法
  • pointer-events: auto; 是一个 CSS 属性,
  • CSS基础:浮动(float)如何使用清楚以及代替方法
  • 使用CJson编写多个节点嵌套的程序代码
  • 尚品汇-延迟插件实现订单超时取消(四十五)
  • Markdown转换成公众号、知乎、今日头条格式,已开源
  • 已经30岁了,想转行从头开始现实吗?什么样的工作算好工作?
  • List 集合指定值升序降序排列Comparator实现
  • 【学习笔记】5G-A时代物联网应用及策略研究
  • 深入了解以太坊
  • 4个实用的微服务测试策略
  • 77. Combinations
  • crontab执行失败的多种原因
  • exif信息对照
  • httpie使用详解
  • in typeof instanceof ===这些运算符有什么作用
  • Intervention/image 图片处理扩展包的安装和使用
  • Java小白进阶笔记(3)-初级面向对象
  • JS+CSS实现数字滚动
  • Laravel 中的一个后期静态绑定
  • react-native 安卓真机环境搭建
  • SpringBoot几种定时任务的实现方式
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 彻底搞懂浏览器Event-loop
  • 机器学习中为什么要做归一化normalization
  • 数据结构java版之冒泡排序及优化
  • 小程序开发中的那些坑
  • 小而合理的前端理论:rscss和rsjs
  • 最简单的无缝轮播
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • # include “ “ 和 # include < >两者的区别
  • # 利刃出鞘_Tomcat 核心原理解析(八)-- Tomcat 集群
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • (C语言)fread与fwrite详解
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (ZT)一个美国文科博士的YardLife
  • (转)EOS中账户、钱包和密钥的关系
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET Core IdentityServer4实战-开篇介绍与规划
  • .Net Core 中间件与过滤器
  • .NET Framework .NET Core与 .NET 的区别
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端