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

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


http://www.kler.cn/news/290297.html

相关文章:

  • Spring6梳理6——依赖注入之Setter注入
  • 【drools】Rulesengine构建及intelj配置
  • 通过组合Self-XSS + CSRF得到存储型XSS
  • 跨境电商代购系统中前台基本功能介绍:帮助更快的了解跨境代购业务
  • 注册登陆(最新版)
  • IOS 18 发现界面(UITableView)Banner轮播图实现
  • 【话题】提升开发效率的秘密武器:探索高效编程工具
  • SpinalHDL之BlackBox(下篇)
  • C#如何使用外部别名Extern alias
  • 单向链表与双向链表
  • 8逻辑回归的代价函数
  • HTTP与TCP的关系是什么?HTTP 的端口有什么意义?
  • ComfyUI SDXL Prompt Styler 简介
  • Android Studio Koala下载并安装,测试helloworld.
  • 惠中科技:以 RDS 光伏自清洁技术开启光伏电站新未来
  • 逻辑学(Logic)
  • Spring常用中间件
  • 智能分拣投递机器人
  • Python的socket库详细介绍
  • TOGAF之架构标准规范-架构愿景
  • Linux基础 -- pthread之线程池任务调度
  • Windows编程系列:PE文件结构
  • 【图论】Dijkstra算法求最短路
  • 【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理
  • 注册安全分析报告:熊猫频道
  • centos 安装使用aria2
  • 数据分析处理库(pandas)
  • 802.11 中 scrambler的matlab仿真
  • Oracle中的临时表Temporary Table
  • [数据集][目标检测]课堂行行为检测数据集VOC+YOLO格式4065张12类别