首页 > 数据库 >利用redis队列抢红包

利用redis队列抢红包

时间:2023-10-11 09:14:58浏览次数:36  
标签:红包 surplus 队列 money 抢红包 redis user key id

    /**
     * 发放红包
     * @param $money //金额
     * @param $num //数量
     * @param $packet //群组id确保key的唯一性
     * @param $user_id //发放人
     * @return array
     */
    public function deliver($num,$packet,$money,$user_id){

        //业务逻辑
        $user = User::get($user_id);
        if($user['money'] < $money){
            $this->error('金额不足');
        }

        //获取金额
        $money_arr = $this->redAlgorithm($money,$num);
        if(!$money_arr){
            $this->error('单个红包金额不可低于0.01');
        }

        //发放红包  入库操作
        $key = 'packet'.$packet;

        //防止对已经设置过的商品库存进行覆盖
        if(!empty($this->redis->llen($key))){
            $this->error('改红包已建立');
        }

        // 将商品存入Redis链表中
        $this->redis->lPush($key,0);
        foreach ($money_arr as $k=>$v){
            $this->redis->lPush($key,$k+1);
            //记录num个红包的金额

        }

        //扣除金额

        //加入流水

        // 延时队列,24小时没有抢则退还 86400
       //  todo 未测时效性(redis不行可切mysql)
      //  Queue::later('86390','app\job\Robexpire',['id'=>$id],'Robexpire');

        $this->success('发放成功');

    }

  

    /**
     * 抢红包
     * @param $user_id //抢红包id
     * @param $packet //群组id,获取key
     * @param $boid //红包id,用来获取已分好金额
     * @return array
     */
    public function rob($user_id,$packet,$boid)
    {

        $key = 'packet'.$packet;

        //已抢购用户队列
        $userBuyKey = 'user_buy'.$packet;

        //该用户id是否在抢购用户集合中
        $userBuyStatus = $this->redis->sismember($userBuyKey,$user_id);

        if($userBuyStatus){
            //该用户已抢过
            $this->error('您已抢过');
        }

        // 从链表的头部删除一个元素,返回删除的元素,因为pop操作是原子性,即使很多用户同时到达,也是依次执行
        //$count为第几个
        $count = $this->redis->lpop($key);
        if(!$count){
            //已抢完
            $this->error('红包已抢完');
        }

        //加入已抢  将用户id添加到抢购成功用户集合中
        $this->redis->sadd($userBuyKey,$user_id);

        //抢成功, 操作数据库

        //增加金额


        //加入流水

        $this->success('抢成功');

    }

  

    /**
     * 获取红包金额
     * @param $money  //金额
     * @param $count  //数量
     * @return array
     */
    function redAlgorithm($money, $count)
    {
        // 参数校验
        if ($count * 0.01 > $money) {
            $this->error('单个红包金额不可低于0.01');
        }
        // 存放随机红包
        $redpack = [];
        // 未分配的金额
        $surplus = $money;
        for ($i = 1; $i <= $count; $i++) {
            // 安全金额
            $safeMoney = $surplus - ($count - $i) * 0.01;
            // 平均金额
            $avg = $i == $count ? $safeMoney : bcdiv($safeMoney, ($count - $i), 2);
            // 随机红包
            $rand = $avg > 0.01 ? mt_rand(1, $avg * 100) / 100 : 0.01;
            // 剩余红包
            $surplus = bcsub($surplus, $rand, 2);
            $redpack[] = $rand;
        }
        // 平分剩余红包
        $avg = bcdiv($surplus, $count, 2);
        for ($n = 0; $n < count($redpack); $n++) {
            $redpack[$n] = bcadd($redpack[$n], $avg, 2);
            $surplus = bcsub($surplus, $avg, 2);
        }
        // 如果还有红包没有分配完时继续分配
        if ($surplus > 0) {
            // 随机抽取分配好的红包,将剩余金额分配进去
            $keys = array_rand($redpack, $surplus * 100);
            // array_rand 第二个参数为 1 时返回的是下标而不是数组
            $keys = is_array($keys) ? $keys : [$keys];
            foreach ($keys as $key) {
                $redpack[$key] = bcadd($redpack[$key], 0.01, 2);
                $surplus = bcsub($surplus, 0.01, 2);
            }
        }
        // 红包分配结果
        return $redpack;
    }

  

标签:红包,surplus,队列,money,抢红包,redis,user,key,id
From: https://www.cnblogs.com/jwyq/p/17756215.html

相关文章

  • 看完包你搞懂Redis缓存穿透、击穿和雪崩!!!说到做到
    缓存穿透缓存穿透是指当用户对Redis发出无效或者不存在的数据信息操作时,这条数据在Redis中不存在,Redis就会在MySQL数据库中查询,可时无效的信息在mysql数据库中也不存在,就会造成Redis一直查询MySQL,对MySQL造成极大压力解决方式方式一:返回缓存空值这种方式有点像“以牙还牙”,对......
  • 阻塞队列
    什么是阻塞队列阻塞队列是一种特殊的队列,它支持线程安全并发操作的同时提供了阻塞操作功能。在阻塞队列中,当队列为空时,从队列中取元素的操作将被阻塞,而当队列已满时,往队列中放元素的操作也会被阻塞。阻塞队列的应用场景阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加......
  • Redis写入反弹连接任务
    Cron表达式cron的增删改查crontab-uroot-r            删除某个用户的任务crontab-uroottime.cron把文件添加到某个用户的任务crontab-uroot-I               列举某个用户的任务crontab-uroot-e               ......
  • Redis淘汰策略-231005
    Redis的内存淘汰策略有哪些:noeviction:当内存不足以容纳新写入数据时,新写入操作会报错;allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的);allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。设置过期时间的键空间......
  • redis集群方案有哪些
       ......
  • Redis事务和持久化
    Redis事务处理流程Redis服务器接受客户端命令检查客户端是否处于事务状态是,则将命令放入事务队列中向客户端返回QUEUED字符串(表示命令已入事务列队)否,则直接执行命令向客户端返回命令执行结果Redis事务错误调用EXEC之前的错误(语法错误/事务回滚)......
  • 分布式锁-实现原理(setnx,redisson)
         ......
  • 07_用队列实现栈
    用队列实现栈【题目】请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop和empty)。对应于leetcode225题实现MyStack类:voidpush(intx)将元素x压入栈顶。intpop()移除并返回栈顶元素。inttop()返回栈顶元素。booleanempty(......
  • 利用Redis生成实时排行榜
            ......
  • 232. 用栈实现队列
    请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:voidpush(intx) 将元素x推到队列的末尾intpop() 从队列的开头移除并返回元素intpeek() 返回队列开头的元素booleanempty() 如果队列为空,返回 true ;否则......