ThinkPHP5.1结合Redis模拟秒杀(悲观锁,乐观锁)
test34()初始化Redis扣库存测试数据
- 每次进行并发测试前,都要执行的初始化
test35无事务,无锁
- 超卖
test36有事务,无锁
- 超卖
test37有事务,watch乐观锁
- 不超卖 数据凌乱,适用读多写少情况
test38有事务,setnx分布式锁
- 不超卖 数据整齐,适用写多读少情况
具体代码如下
public function test34()
{
Redis::set('stock', 10000);
Redis::del('queue');
//以上两行代码,为不加锁代码,并发情况下,抢到相同库存号;
Redis::set('stock_transaction', 50);
Redis::del('queue_transaction');
Redis::del('exec');
Redis::set('count',0);//判定大于零次数
Redis::set('buyer',0);//买家个数
Redis::del('lock:stock');//释放锁
echo '初始化Redis扣库存测试数据<br/>';
echo 'test35无事务,无锁<br/>';
echo 'test36有事务,无锁<br/>';
echo 'test37有事务,watch乐观锁<br/>';
echo 'test38有事务,setnx分布式锁<br/>';
}
//抢库存 无锁 并发情况下,抢到相同库存号;
public function test35()
{
$stock = Redis::get('stock');
Redis::decr('stock');
Redis::lpush('queue', $stock);
echo "抢到第 $stock 个";
}
//抢库存,事务,无锁,超卖了
/**
* 注意,Redis事务中,不能和PHP进行交互 但是可以exec后,会返回事务中各条命令的值
*
* 命令错误 事务全部命令不执行
* 命令操作对象错误 事务全部命令执行
*/
public function test36()
{
Redis::incr('buyer');//买家个数
$buy=1;//循环标志
do {
if (Redis::get('stock_transaction')>0){
Redis::incr('count');//判定大于零次数
usleep(rand(0,100000));//给时间给别人改
Redis::multi();
Redis::decr('stock_transaction');//扣库存 //返回扣库存后的值
Redis::lpush('queue_transaction',1);//用于统计执行次数,实际业务可以用于存放Uid 返回内部元素个数
$ok=Redis::exec();//返回事务内,所有命令的返回值,按命令先后排序,当操作别打断时,返回空
if (is_array($ok)){
Redis::lpush('exec',json_encode($ok));
echo '买到了第'.($ok[0]+1).'个';
$buy=0;
}else{
usleep(rand(0,100000));
}
//这里要解决被别人改掉以后,重试问题
}else{
echo '已经销售一空了';
$buy=0;
}
}while($buy);
}
//watch锁加事务 不会超卖 乐观锁,适用于读多写少的情况 得到的响应数据,比较凌乱
public function test37(){
Redis::incr('buyer');//买家个数
$buy=1;//循环标志
do {
Redis::watch('stock_transaction');
if (Redis::get('stock_transaction')>0){
Redis::incr('count');//判定大于零次数
usleep(rand(0,100000));//给时间给别人改
Redis::multi();
Redis::decr('stock_transaction');//扣库存 //返回扣库存后的值
Redis::lpush('queue_transaction',1);//用于统计执行次数,实际业务可以用于存放Uid 返回内部元素个数
$ok=Redis::exec();//返回事务内,所有命令的返回值,按命令先后排序,当操作别打断时,返回空
if (is_array($ok)){
Redis::lpush('exec',json_encode($ok));
echo '买到了第'.($ok[0]+1).'个';
$buy=0;
}else{
usleep(rand(0,100000));
}
//这里要解决被别人改掉以后,重试问题
}else{
echo '已经销售一空了';
$buy=0;
}
}while($buy);
}
//redis 悲观锁模拟抢购,适用于写多,读少的情况 悲观锁,得到的响应数据,很整齐划一
public function test38(){
Redis::incr('buyer');//买家个数
$buy=1;//循环标志
do {
if ($this->redisLock() and Redis::get('stock_transaction')>0){//注意,这里的获取锁,需要放到前面,否则超卖
Redis::incr('count');//判定大于零次数
usleep(rand(0,100000));//给时间给别人改
Redis::multi();
Redis::decr('stock_transaction');//扣库存 //返回扣库存后的值
Redis::lpush('queue_transaction',1);//用于统计执行次数,实际业务可以用于存放Uid 返回内部元素个数
$ok=Redis::exec();//返回事务内,所有命令的返回值,按命令先后排序,当操作别打断时,返回空
if (is_array($ok)){
Redis::lpush('exec',json_encode($ok));
echo '买到了第'.($ok[0]+1).'个';
$buy=0;
}else{
usleep(rand(0,100000));
}
//这里要解决被别人改掉以后,重试问题
}else{
echo '已经销售一空了';
$buy=0;
}
$this->redisUnlock();
}while($buy);
}
//redis 库存实验获得锁
public function redisLock(){
do{
$lock=Redis::setnx('lock:stock',1);//不存在,执行操作,返回1,即上锁 ,存在则不操作,返回0;
if ($lock){
Redis::expire('lock:stock',60);//60s过期 防止死锁
return true;
}else{
usleep(rand(0,100000));
}
}while(1);
}
//redis 库存实验解除锁
public function redisUnlock(){
Redis::del('lock:stock');
}