php拼团逻辑,拼团3人团避免人数过多的一个算法

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

一、基础实现

下面是V1版本代码:

/**

* 锁定某个拼团,去尝试锁定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}');//用户锁定的

二、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个Redis

'SPELL_GROUP_LOCK_TEAM_LIST', 'user_lock_team_list_{#teamId}');//团锁定的队列

'SPELL_GROUP_LOCK_INCR_V2', 'lock_team_incr_v2_{#teamId}');//同一时刻一个团只允许一个人操作

三、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}');//同一时刻一个团只允许一个人操作

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值