PHP商品秒杀问题解决方案实例详解【mysql与redis】(3)
基于redis的解决方案
基于watch的乐观锁方案
watch用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。这种方案跟mysql中的乐观锁方案类似,具体表现也是一样的。
$num = $this->redis->get('num');
if($num > 0) {
$this->redis->watch('num');
usleep(100);
$res = $this->redis->multi()->decr('num')->lPush('result',$num)->exec();
if($res == false){
echo "fail1";
}else{
echo "success:".$num;
}
}else{
echo "fail2";
}
基于list的队列方案
基于队列的方案利用了redis出队操作的原子性,抢购开始之前首先将商品编号放入响应的队列中,在抢购时依次从队列中弹出操作,这样可以保证每个商品只能被一个进程获取并操作,不存在超发的情况。该方案的优点是理解和实现起来都比较简单,缺点是当商品数量较多是,需要将大量的数据存入到队列中,并且不同的商品需要存入到不同的消息队列中。
public function init(){
$this->redis->del('goods');
for($i=1;$i<=10;$i++){
$this->redis->lPush('goods',$i);
}
$this->redis->del('result');
echo 'init done';
}
public function run(){
$goods_id = $this->redis->rPop('goods');
usleep(100);
if($goods_id == false) {
echo "fail1";
}else{
$res = $this->redis->lPush('result',$goods_id);
if($res == false){
echo "writelog:".$goods_id;
}else{
echo "success".$goods_id;
}
}
}
基于decr返回值的方案
如果我们将剩余量num设置为一个键值类型,每次先get之后判断,然后再decr是不能解决超发问题的。但是redis中的decr操作会返回执行后的结果,可以解决超发问题。我们首先get到num的值进行第一步判断,避免每次都去更新num的值,然后再对num执行decr操作,并判断decr的返回值,如果返回值不小于0,这说明decr之前是大于0的,用户抢购成功。
public function run(){
$num = $this->redis->get('num');
if($num > 0) {
usleep(100);
$retNum = $this->redis->decr('num');
if($retNum >= 0){
$res = $this->redis->lPush('result',$retNum);
if($res == false){
echo "writeLog:".$retNum;
}else{
echo "success:".$retNum;
}
}else{
echo "fail1";
}
}else{
echo "fail2";
}
}
基于setnx的排它锁方案
redis没有像mysql中的排它锁,但是可以通过一些方式实现排它锁的功能,就类似php使用文件锁实现排它锁一样。
setnx实现了exists和set两个指令的功能,若给定的key已存在,则setnx不做任何动作,返回0;若key不存在,则执行类似set的操作,返回1。我们设置一个超时时间timeout,每隔一定时间尝试setnx操作,如果设置成功就是获得了相应的锁,执行num的decr操作,操作完成删除相应的key,模拟释放锁的操作。
