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次, -n代表请求数,-c代表并发数

再查看库存:
靠,日了狗了,库存变成了-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一个,队列为空就判断没有了,推荐这种更稳当。