拼团3人团避免人数过多的一个算法

咱们有个需求就是3人成一个团,发起者开团,也就是剩余2我的能够参与拼团,但实际上会碰到这种状况,这个团若是进入的人过多都购买引发这个团实际超出3人的状况。所以每一个用户在进入的时候有个锁定名额的逻辑,一我的进去就会锁定五分钟,我以前作了个版本,可是代码比较复杂,php

1、基础实现redis

下面是V1版本代码:json

/**
     * 锁定某个拼团,去尝试锁定1,2个名额,都被锁定则则返回失败
     * @param $open_id
     * @param $active_id
     * @param $team_id
     * @return int
     */
    public function lockTeam1($active_id,$team_id,$open_id,$team_user_count=1){
        $seetPosition=[];
        //计算还有几个名额剩下
        for($i=1;$i<=3;$i++){
            if($i<=$team_user_count){
                continue;
            }
            array_push($seetPosition,'seet_'.$i);
        }
        //返回第一个未锁定的名额
        $keyTemplateSeat = SPELL_GROUP_LOCK_TEAM;
        $input = ['teamId'=>$team_id];//第一个拼团名额
        $teamKey = $this->swtichRedisKey($input, $keyTemplateSeat);
        $unlockSeat='';
        $lockTimeArr=[];
        $firstLockTime=0;
        foreach($seetPosition as $val){
            //echo $teamKey.$val."\n";
            $lockInfo=$this->getRedis()->WYget($teamKey.$val);
            if($lockInfo){
                array_push($lockTimeArr,$lockInfo);//将全部当时锁定的日期时间戳放入锁定数组
                continue;
            }
            else{
                $unlockSeat=$val;
                break;
            }
        }
        if(!empty($lockTimeArr)){
            sort($lockTimeArr);//按从小到大排序
            $firstLockTime=$lockTimeArr[0];//更新为最早锁定时间
        }
        //读取用户锁定的team
        $keyTemplate = SPELL_GROUP_LOCK_TEAM_USER;
        $input = ['openId'=>$open_id];
        $userLockKey = $this->swtichRedisKey($input, $keyTemplate);
        $userLockedTeam=$this->getRedis()->WYhGet($userLockKey,'team_id');//读取用户锁定的Team
        //若是用户锁定过这个team则直接返回
        if($userLockedTeam==$team_id){
            $ret=array('code'=>1,'msg'=>'用户锁定的跟以前锁定的位置是一个团');
            return $ret;
        }
        if($unlockSeat==''){
            //团已经满3人了或锁定人数满了
            $ret=array('code'=>0,'msg'=>'这个团已经满员了');
            if($firstLockTime){
                $sUnLockTime=$firstLockTime+self::LOCK_TEAM_EXPIRE;
                $ret['data']=array(
                    'sTeamId'=>$team_id,
                    'sActiveId'=>$active_id,
                    'sLockTime'=>$firstLockTime,
                    'sUnLockTime'=>(string)$sUnLockTime,
                );
            }
            return $ret;//没有空位
        }
        //开始锁定
        $keyTemplate = SPELL_GROUP_LOCK_INCR;
        $input = ['teamId'=>$team_id];//
        $lockredisKey = $this->swtichRedisKey($input, $keyTemplate);
        //这个团的这个位置只能锁定一次,不然就是锁定人数过多
        if($this->getRedis()->WYincr($lockredisKey.$unlockSeat)==1){
            //锁定过其余团先解锁其余团
            if($userLockedTeam){
                $userLockPostion=$this->getRedis()->WYhGet($userLockKey,'team_pos');//读取用户锁定的position
                $input = ['teamId'=>$userLockedTeam];
                $unlockteamKey = $this->swtichRedisKey($input, $keyTemplateSeat);//须要解锁的团的key名
                $this->getRedis()->WYdelete($unlockteamKey.$userLockPostion);//解锁用户锁定的团锁定的位置
            }
            $this->getRedis()->WYset($teamKey.$unlockSeat,time(),self::LOCK_TEAM_EXPIRE);//标识这个团的这个位置被锁定了
            $this->getRedis()->WYhMset($userLockKey,array('team_id'=>$team_id,'team_pos'=>$unlockSeat),self::LOCK_TEAM_EXPIRE);//设置用户锁定的团为当前团及锁定位置
            $this->getRedis()->WYdelete($lockredisKey.$unlockSeat);//解除INCR
            $ret=array('code'=>1,'msg'=>'用户['.$open_id.']已经成功锁定');
            return $ret;
        }
        //同时争抢这个位置的人过多
        $ret=array('code'=>2,'msg'=>'锁定的人数已满');
        return $ret;
    }

一共用了以下的Redis,第一个是避免高并发的string,第二个SPELL_GROUP_LOCK_TEAM是个string,这个是保存了团的某个位置的锁定时间(teamid_seat_1,teamid_seat_2),第三个就是用户的锁定信息,用于解锁团,这个版本操做的Redis比较多,可靠性还能够,就是逻辑比较复杂,通常人看不懂。数组

'SPELL_GROUP_LOCK_INCR', 'lock_team_incr_{#teamId}');//避免对团的锁定多用户同时
'SPELL_GROUP_LOCK_TEAM', 'user_lock_team_{#teamId}');//团的锁定位置
'SPELL_GROUP_LOCK_TEAM_USER', 'lock_team_user_new_{#openId}');//用户锁定的并发

2、list版本高并发

下面这个版本算是重构版,代码简洁点,用list结构保存了用户的每次锁团信息,一次性所有读取出来而后根据时间判断,将全部过时的信息移除队列,这个版本已经很优化了,减小了很多KEY,这个版本没有去考虑用户去锁定其余团的时候解锁当前团的问题,须要优化下:优化

/**
     * V2版本锁团,还未验证
     * @param $active_id
     * @param $team_id
     * @param $open_id
     * @param int $team_user_count
*/
public function lockTeam($active_id,$team_id,$open_id,$team_user_count=1){
    //开始锁定
    $keyTemplate = SPELL_GROUP_LOCK_INCR_V2;
    $input = ['teamId'=>$team_id];
    $lockTeamKey = $this->swtichRedisKey($input, $keyTemplate);
    //同一时刻这个团只容许一我的操做,避免人数过多引发错误
    if($this->getRedis()->WYincr($lockTeamKey)==1) {
        $keyTemplate = SPELL_GROUP_LOCK_TEAM_LIST;
        $input = ['teamId'=>$team_id];
        $UserLockTeamKey = $this->swtichRedisKey($input, $keyTemplate);
        $length = $this->getRedis()->WYlLen($UserLockTeamKey);//读取队列的长度
        $time = time();
        $flag = false;
        if ($length) {
            $lockData = $this->getRedis()->WYlRange($UserLockTeamKey);//由于key自己不大,lrange没有多大开销
            krsort($lockData);//将取出的数据倒排,便于将过时的key移除
            foreach ($lockData as $val) {
                $lData = json_decode($val, true);
                //当前用户再次锁定而且没有过时则直接返回,若是有未过时的锁定则直接返回
                if (($lData['open_id'] == $open_id) && ($time <= $lData['lock_time'] + self::LOCK_TEAM_EXPIRE)) {
                    $flag = true;
                }
                //过时的数据清理掉
                if ($time > $lData['lock_time'] + self::LOCK_TEAM_EXPIRE) {
                    $this->getRedis()->WYrPop($UserLockTeamKey);
                }
            }
            $length = $this->getRedis()->WYlLen($UserLockTeamKey);//获取新的队列长度
        }
        //当前用户存在未过时的锁定,直接能够返回
        if ($flag) {
            $ret=array('code' => 1, 'msg' => '用户[' . $open_id . ']存在未过时的锁定');
        }
        else{
            $maxListLength = 3 - $team_user_count;//队列容许的最大长度为总数减去剩余未支付人数
            $data = json_encode(array('open_id' => $open_id,'lock_time' => $time));
            if ($maxListLength > $length) {
                $this->getRedis()->WYlPush($UserLockTeamKey, $data);//未满就直接插入
                $ret=array('code' => 1, 'msg' => '用户[' . $open_id . ']成功锁定');
            } else {
                $ret=array('code' => 0, 'msg' => '锁定人数过多');
            }
        }
        $this->getRedis()->WYdelete($lockTeamKey);//解除INCR
        return $ret;
    }
    return array('code' => 0, 'msg' => '同时操做的人太多了');
}

使用了以下Redis,若是加上用户,也是3个Redisthis

'SPELL_GROUP_LOCK_TEAM_LIST', 'user_lock_team_list_{#teamId}');//团锁定的队列
'SPELL_GROUP_LOCK_INCR_V2', 'lock_team_incr_v2_{#teamId}');//同一时刻一个团只容许一我的操做code

3、zset版本排序

下面这个版本是list版本的优化版,用zset储存了用户的参与时间,利用zset自然的排序功能,不用再次排序,而且删除用户锁定的团也是很容易的事情:

/**
     * V2版本锁团,还未验证
     * @param $active_id
     * @param $team_id
     * @param $open_id
     * @param int $team_user_count
     */
    public function lockTeam($active_id,$team_id,$open_id,$team_user_count=1){
        //开始锁定
        $keyTemplate = SPELL_GROUP_LOCK_INCR_V2;
        $input = ['teamId'=>$team_id];
        $lockTeamKey = $this->swtichRedisKey($input, $keyTemplate);
        $firstLockTime='';//第一个锁定人的锁定时间
        //同一时刻这个团只容许一我的操做,避免人数过多引发错误
        if($this->getRedis()->WYincr($lockTeamKey)==1) {
            //读取用户锁定的团,若是存在则删除
            $keyTemplate = SPELL_GROUP_LOCK_TEAM_USER_V2;
            $input = ['openId'=>$open_id];
            $userLockTeamKey = $this->swtichRedisKey($input, $keyTemplate);
            $userLockTeam = $this->getRedis()->WYget($userLockTeamKey);//读取用户锁定的团

            $keyTemplate = SPELL_GROUP_LOCK_TEAM_ZSETS;
            $input = ['teamId'=>$team_id];
            $LockTeamSetsKey = $this->swtichRedisKey($input, $keyTemplate);
            //当用户已经锁定过而且锁定的不是当前的团的时候,将以前锁定的删除掉
            if($userLockTeam && $userLockTeam!=$team_id){
                $this->getRedis()->WYzRem($LockTeamSetsKey,$open_id);//将用户锁定的其余团解锁
            }
            $length = $this->getRedis()->WYzCard($LockTeamSetsKey);//读取队列的长度
            $time = time();
            $flag = false;
            if ($length) {
                $lockData = $this->getRedis()->WYzRange($LockTeamSetsKey,0,-1,1);//查询score
                foreach ($lockData as $key=>$val) {
                    //读取并设定第一个锁定人的锁定时间
                    if($firstLockTime==''){
                        $firstLockTime=$val;
                    }
                    //当前用户再次锁定而且没有过时则直接返回,若是有未过时的锁定则直接返回
                    if (($key == $open_id) && ($time <= $val + self::LOCK_TEAM_EXPIRE)) {
                        $flag = true;
                    }
                    //过时的数据清理掉
                    if ($time > $val + self::LOCK_TEAM_EXPIRE) {
                        $this->getRedis()->WYzRem($LockTeamSetsKey,$key);
                    }
                }
                $length = $this->getRedis()->WYzCard($LockTeamSetsKey);//获取新的队列长度
            }
            //当前用户存在未过时的锁定,直接能够返回
            if ($flag) {
                $ret=array('code' => 1, 'msg' => '用户[' . $open_id . ']存在未过时的锁定');
            }
            else{
                $maxListLength = 3 - $team_user_count;//队列容许的最大长度为总数减去剩余未支付人数
                if ($maxListLength > $length) {
                    $this->getRedis()->WYzAdd($LockTeamSetsKey, $time,$open_id);//未满就直接插入
                    $this->getRedis()->WYexpire($LockTeamSetsKey,self::TEAM_EXPIRE);//设置过时时间,有人操做会自动延时,不然会过时
                    $this->getRedis()->WYset($userLockTeamKey,$team_id,self::LOCK_TEAM_EXPIRE);//设置用户当前锁定的团,有效期跟锁定团的有效期相同
                    $ret=array('code' => 1, 'msg' => '用户[' . $open_id . ']成功锁定');
                } else {
                    //print_r($this->getRedis()->WYzRange($LockTeamSetsKey,0,-1,1));
                    $ret=array('code' => 0, 'msg' => '锁定人数过多');
                    if($firstLockTime){
                        $sUnLockTime=$firstLockTime+self::LOCK_TEAM_EXPIRE;
                        $ret['data']=array(
                            'sTeamId'=>$team_id,
                            'sActiveId'=>$active_id,
                            'sLockTime'=>(string)$firstLockTime,
                            'sUnLockTime'=>(string)$sUnLockTime,
                        );
                    }
                }
            }
            $this->getRedis()->WYdelete($lockTeamKey);//解除INCR
            return $ret;
        }
        return array('code' => 0, 'msg' => '同时操做的人太多了');
    }

使用了以下redis的:

'SPELL_GROUP_LOCK_TEAM_ZSETS', 'user_lock_team_zset_{#teamId}');//团锁定的有序集合 'SPELL_GROUP_LOCK_TEAM_USER_V2', 'lock_team_user_v2_{#openId}');//用户当前锁定的团 'SPELL_GROUP_LOCK_INCR_V2', 'lock_team_incr_v2_{#teamId}');//同一时刻一个团只容许一我的操做

相关文章
相关标签/搜索