最近在做一个抢礼券的小需求,需求功能点很简单,后台录入礼券,导入礼券码,前台用户抢礼券。但是还是要考虑一些问题的,例如用户抢礼券时如何保证多个用户并发操作时礼券的分配问题,是否会导致一个礼券码被多个用户抢到,还有考虑大并发情况下数据库的承受能力。考虑到公司流量不是特别大的情况下做了如下两点优化操作:
一、用redis生成限制锁
用redis做成限制锁,在多用户同时请求的情况下只处理一个用户的请求,其他用户等待锁,这样就把多线程变成单线程请求了,同样可以避免同一礼券分配给多用户。
/**
* 限制锁
*
* @return bool
*/
public function getlock()
{
$reTimes = 5;
while($reTimes > 0){
$this->expireTime = $this->microtime_float()+2;
if($this->redis->setNx($this->key, $this->expireTime)){
return true;
}
$setExpireTime = $this->redis->get($this->key);
if($setExpireTime !== false && $setExpireTime < time()+1){
//防止锁竞争,获取时间跟原定过期时间一致的获得锁
$oldTime = $this->redis->getSet($this->key, $this->microtime_float()+2);
if($oldTime == $setExpireTime){
return true;
}
}
$reTimes--;
usleep(10000);
}
return false;
}
其实在使用事务时因为事务有行级锁,同样可以避免同一个礼券码分配给多用户。
二、使用redis实现队列防止高并发情况下“等锁”导致大批线程等不到锁“死掉”
把所有要发出去的礼券码使用lpush加入队列,然后每次去请求就lpop出队列,这样就大并发情况下不用频繁的去查询数据库。同样在后台关闭这个礼券的时候会清除掉对应队列
//判断该用户是否抢过该礼券
$code = $conn->fetchColumn('select * from table_name');
if(empty($code)){
//判断队列里是否还含有礼券码
if(false === ($codeInfo = $this->redis->lpop($ticketListKey))){
$tickList = $conn->fetchAll('select t.id code_id, t.coupon_code from table_name');
if(!empty($tickList)){
//把礼券加入队列
foreach($tickList as $key => $val){
$this->redis->lpush($ticketListKey, json_encode(array('id' => $val['code_id'] , 'code' => $val['coupon_code'])));
}
$codeInfo = $this->redis->lpop($ticketListKey);
}
}