道者编程


php redis高并发秒杀方案

一:项目方案:

1:需求:某个商品参加秒杀,数量100个。

2:思路:redis弄一个计算器,提前写入100,抢购一个数量减1,直到0位置,抢购完毕。

二:代码实现

1:在redis创建一个名称为:inventory的key

set inventory 100
查看一下:

数量:100,OK

2:php代码

<?php

date_default_timezone_set("PRC");
$redis = new Redis();
$redis->connect('192.168.0.25',6379); 
$redis->auth('123');  

$count = $redis->get('inventory'); //获取库存数量
if($count>0){ //抢购成功
	 $redis->decr("inventory"); //库存减1
}else{
	echo '没有了';
}
运行一下,通过 get查看:

数量少了1个,正常,看起来没问题。

再用ab并发测试一下,1000个并发,请求10000次


再查看库存:

靠,日了狗了,库存变成了-2,这就是高并发下的问题。

3:用一下事物,据说redis的事物具有原子性

先看看官方的解释:事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。

<?php

date_default_timezone_set("PRC");
$redis = new Redis();
$redis->connect('192.168.0.25',6379); 
$redis->auth('123');  

$count = $redis->get('inventory'); //获取库存数量

$redis->multi(); //开始事物
if($count>0){ //抢购成功
	 $redis->decr("inventory"); //库存减1
	 $redis->exec(); //执行事物
}else{
	echo '没有了';
}

把inventory重新设置为100,同样的并发再干一次,结果很遗憾,还是出现了负数。

感觉redis事物原子性应该是这样的,虽然是保证一条条执行,但所有请求还是会执行,只是在事物队列中一个一个执行,除非命令出现问题,或者执行不成功就直接报错,一条命令不成功,所有都不执行,redis不会回滚,你要自己收拾烂摊子,事物只是保证命令按顺序执行而已。

4:接着干,事物加锁,watch乐观锁

<?php

date_default_timezone_set("PRC");
$redis = new Redis();
$redis->connect('192.168.0.25',6379); 
$redis->auth('123');  

$count = $redis->get('inventory'); //获取库存数量

if($count>0){ //抢购成功
	$redis->watch("inventory"); //加锁,如果当前的inventory被其他用户修改或者删除,那么后续的decr就不执行,保证不会同时修改
	$redis->multi(); //开始事物

	$redis->decr("inventory"); //库存减1

	$redis->exec(); //执行事物
}else{
	echo '没有了';
}

ab测试一下,没有出现负数,再加到5000个并发,还是没有出现负数,证明OK。大概原理是这样,真实环境要复杂的多。

WATCH 命令提供了在开始事务前监视一个或多个键的能力。如果这些键中的任何一个在执行事务前发生改变,整个事务就会被取消并抛出 WatchError 异常。这就是为什么要和事物同时用,因为事物从multi开始,到exec才会执行,如果不使用事物,那么监控到其他用户操作也没用,WATCH监控到后,命令还没有执行,事物会把当前所有命令直接取消。

5:用队列也可以,队列操作本身就是原子性的,不存在几个同时进入,但是判断队列总数会有并发冲突

<?php

date_default_timezone_set("PRC");
$redis = new Redis();
$redis->connect('192.168.0.25',6379); 
$redis->auth('123');  
$push_user = 'push_user'; //队列名称 List数据类型
if($redis->llen($push_user)<100){
	$redis->watch($push_user); //加锁,监控队列是否变化
	$redis->multi(); //开始事物


	$redis->rpush($push_user,1); //这里固定用户id


	$redis->exec(); //执行事物
}else{
	echo '没有了';
}

 经过测试没问题!不过这种在后续逻辑处理要注意下,出队列操作,会减少队列的数据,这样队列又会进入新数据。

还有种方式使用队列:把业务逻辑写进去,比如这里100个商品数量添加到队列里面,进来一个lpop一个,队列为空就判断没有了,推荐这种更稳当。


最新评论:
1楼 广东省深圳市 电信ADSL 发表于 2018-08-29 17:48:47
6666666
3楼 中国 移动 发表于 2018-10-14 20:38:29
楼主使用的第4中方法还是会出现负数的,但是吧watch放在get前面就可以了,至少我这边测试没有出负数的情况,正常来说也应该先watch,不然的话,在get后,watch前可能会发生变化,不知道我这样理解对不对,感谢楼主的文章
共有 2 条记录  首页 上一页 下一页 尾页 1
我要评论:

看不清楚


链接