简述

抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢?

常规写法:

查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数

这里我就只谈redis的解决方案

我们先来看以下php代码是否能正确解决超抢/卖的问题:

<?php$redis = new Redis();
$redis->connect('127.0.0.1', 6379); //系统库存量$num = 10;  //当前抢购用户id,模拟数据$user_id =  rand(0,100);//检查库存,order:1 定义为键名$len = $redis->llen('order:1');  if($len >= $num)return '已经抢光了哦';//把抢到的用户存入到列表中
$result =$redis->lpush('order:1',$user_id); if($result)return '恭喜您!抢到了哦';

如果代码正常运行,按照预期理解的是列表order:1中最多只能存储10个用户的id,因为库存只有10个。
然而,但是,在使用jmeter工具模拟多用户并发请求时,最后发现order:1中总是超过10个用户,也就是出现了“超卖”。

分析问题就出在这一段代码:

 $len = $redis->llen('order:1');  if($len >= $num)return '已经抢光了哦';

使用redis的原子操作pop

在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过(条件同时被满足了),这三个用户都能抢购成功。而实际上只剩下一件库存可以抢了。
在高并发下,很多看似不大可能是问题的,都成了实际产生的问题了。要解决“超抢/超卖”的问题,核心在于保证检查库存时的操作是依次执行的,再形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。
我们需要使用redis的原子操作来实现这个“单线程”。首先我们把库存存在goods_store:1这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅只是代表一件库存。抢购开始后,每到来一个用户,就从goods_store:1中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:
比如这里我先把库存(假设有10件)放入redis队列:

$redis = new redis();
$redis->connect('127.0.0.1', 6379);//库存
$num=10;//检查库存,goods_store:1 定义为健名
$len=$redis->llen('goods_store:1'); //实际库存-被抢购的库存 = 剩余可用库存
$count = $num-$len; for($i=0;$i<$count;$i++)//往goods_store列表中,未抢购之前这里应该是默认滴push10个库存数了
$redis->lpush('goods_store:1',1);

好吧,抢购时间到了:

$redis = new redis();
$redis->connect('127.0.0.1', 6379);
$user_id =  rand(0,100);//当前抢购用户id
/* 模拟抢购操作,抢购前判断redis队列库存量 */
$count=$redis->lpop('goods_store:1');
if(!$count)return '已经抢光了哦';$result = $redis->lpush('order:1',$user_id);
if($result)return '恭喜您!抢到了哦';

注意:这里可以不必进行数据库操作,而是先存入队列,操作数据库的时间是跟用户无关的,所以应该立马返回让用户知道是否抢到,之后再对这个队列进行操作。

为了检测实际效果,我使用jmeter工具模拟100、500、1000个用户并发进行抢购,经过大量的测试,最终抢购成功的用户始终为10,没有出现“超卖”。

问题:上面虽然能够解决超卖的现象,但是却不能够防止超抢的情况发生,就是一个用户可以抢 到相同的多件商品

尝试解决

$data = $redis->lRange('order:1',0,-1);  //把抢到的用户存入到列表中
if(!in_array($user_id,$data))
{$count=$redis->lpop('goods_store:1');if(!$count)return '已经抢光了哦';$result = $redis->lpush('order:1',$user_id);if($result)return '恭喜您!抢到了哦';
}
else
{return '已经抢购了哦';
}

上面这个代码在没有高并发情况下测试没有问题,但是如果高并发情况下呢。答案是不可以的。这跟上面的

$len = $redis->llen('order:1');  if($len >= $num)return '已经抢光了哦';

是一样道理的,所以行不通

这时有人提出了

$data = $redis->lRange('order:1',0,-1);  //把抢到的用户存入到列表中
if(!in_array($user_id,$data))
...

这两行代码不就是判断list中某个值是否存在吗?为何不直接调用list的exist函数判断,我刚开始也照着这样去查找。不过并没有找到这个内置函数。而我也自己写了一个函数判断是否存在list中,伪代码如下

1. if(调用函数判断id是否存在list中)
2. {3.  $count=$redis->lpop('goods_store:1');
4.  if(!$count)
5.    return '已经抢光了哦';
6.  $result = $redis->lpush('order:1',$user_id);
7.  if($result)
8.    return '恭喜您!抢到了哦';
9.  }
10. else
11. {12.    return '已经抢购了哦';
13. }

不过答案还是不行的,因为如果同时有两个相同id进入if判断,还是都会进入到if中。

解决
这时我将list转成了hash,将代码改成了下面

//把所有用户都插入到这个队列中
$wait_key = "user_wait:2";//真正抢到的用户信息队列
$user_key = "user:1";//库存队列
$store_key = "goods_store:1";$result =$redis->hset($wait_key, $user_id, $user_id);if($result){$count = $redis->lpop($store_key);if (!$count)echo '已经抢光了哦'.$user_id;else{$result =$redis->hset($user_key, $user_id, $user_id);echo '恭喜您!抢到了哦'.$user_id;}}

这时又有人说这样干嘛不可以用list,代码如下:

//把所有用户都插入到这个队列中
$wait_key = "user_wait:2";//真正抢到的用户信息队列
$user_key = "user:1";//库存队列
$store_key = "goods_store:1";
$result =$redis->rPush($wait_key, $user_id);if($result){$count = $redis->lpop($store_key);if (!$count)echo '已经抢光了哦'.$user_id;else{$result =$redis->rPush($user_key,  $user_id);echo '恭喜您!抢到了哦'.$user_id;}}

对比

list:

hash:

分析:list中的值是可以重复的,而hash里面的值是不可以重复的
所以

$result =$redis->rPush($wait_key, $user_id);

$result =$redis->hset($wait_key, $user_id, $user_id);

当高并发的的情况下,无论id是否相同,list的rpush返回结果都是1,而hash的hset只有不同的时候才返回1.这样就可以避免由于高并发而导致一个用户抢到多件同种商品

测试结果

先加入10个库存

$user_id =  1;//当前抢购用户id$wait_key = "user_wait:2";$user_key = "user:1";$store_key = "goods_store:1";$result =$redis->hset($wait_key, $user_id, $user_id);if($result){$count = $redis->lpop($store_key);if (!$count)echo '已经抢光了哦'.$user_id;else{$result =$redis->hset($user_key, $user_id, $user_id);echo '恭喜您!抢到了哦'.$user_id;}}else{echo '已经抢到了'.$user_id;}

为了测试极限高并发情况下,我直接将用户Id设置为1

我这里模拟1000个用户同时进入秒杀


如果秒杀成功,应该是库存为9,而真正的抢购队列只有用户1

使用jemter测试结果


结果也是符合预期的

现在来模拟真正不同id看看是否只是10个用户能抢到

先补充仓库

所有抢购用户


真正抢购到

至此,已经算是实现了预期

总结

在此过程中,我在list那里卡住了一两个小时,之后用hash解决了,在我看来是解决了

简单实现redis实现高并发下的抢购/秒杀功能相关推荐

  1. java redis实现抢购_【抢购/秒杀】redis实现高并发下的抢购/秒杀功能

    问题: 抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否 ...

  2. PHP redis秒杀返回结果,php结合redis实现高并发下的抢购、秒杀功能

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个: 1 高并发对数据库产生的压力 2 竞争状态下如何解决库存的正确减少("超卖"问题) 对于第一个问题,已经很容易想到 ...

  3. php结合redis实现高并发下的抢购、秒杀功能

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个: 1 高并发对数据库产生的压力 2 竞争状态下如何解决库存的正确减少("超卖"问题) 对于第一个问题,已经很容易想到 ...

  4. 抢购活动php,php结合redis实现高并发下的抢购、秒杀功能

    使用redis队列,因为pop操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用(mysql事务在高并发下性能下降很厉害,文件锁的方式也是)先将商品库存如队列<?php $store= ...

  5. PHP 结合redis实现高并发下抢购、秒杀

    抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个: 1 高并发对数据库产生的压力 2 竞争状态下如何解决库存的正确减少("超卖"问题) 对于第一个问题,已经很容易想到 ...

  6. php post发微博,php结合redis实现高并发下发帖、发微博的方法

    本篇文章主要介绍php结合redis实现高并发下发帖.发微博的方法,感兴趣的朋友参考下,希望对大家有所帮助. 发帖.发微博.点赞.评论等这些操作很频繁的动作如果并发量小,直接入库是最简单的 但是并发量 ...

  7. PHP和Redis实现在高并发下的抢购及秒杀功能示例详解

    抢购.秒杀是平常很常见的场景,面试的时候面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等. 抢购.秒杀实现很简单,但是有些问题需要解决,主要针对两个问题: 一.高并发对数据库产生的压力 二 ...

  8. Springboot整合Redis,高并发下访问缓存与写入缓存

    1.配置Redis连接:添加pox.xml依赖: <!-- redis --><dependency><groupId>org.springframework.bo ...

  9. go http 并发数限制_618临近,Redis优化高并发下的抢枪抢买买买性能

    随着618的临近,各种促销活动开始变得热门起来,比较主流的有秒杀.抢优惠券.拼团等等.涉及到高并发争抢同一个资源的主要场景有秒杀和抢优惠券. 本文内容 使用Redis优化高并发场景下的接口性能 数据库 ...

最新文章

  1. Android 动画 ViewPropertyAnimator 的使用
  2. Java多线程的实现方式-Thread 类,Runnable 接口
  3. 当我们说“分类”的时候,我们到底在说什么?
  4. laravel 淘宝 NPM 镜像
  5. 【java】两个线程如何交替执行,一个输出偶数一个输出奇数?
  6. oracle安装后再建库,oracle-数据库的安装与建库
  7. ESPNet: Efficient Spatial Pyramid of Dilated Convolutions for Semantic Segmentation(自动驾驶领域轻量级模型)
  8. 随机森林分类+随机森林回归+实例:用随机森林回归填补缺失值+机器学习调参思想+实例:随机森林在乳腺癌数据上的调参
  9. B站视频下载方法(4K60帧)
  10. 金融行业数据容灾架构中的数据复制技术
  11. [MICCAI2019] A Partially Reversible U-Net for Memory-Efficient Volumetric Image Segmentation
  12. 无人机航拍拍摄制作VR全景图教程
  13. 从头开始训练一个 NER 标注器
  14. 开机输入用户名密码时弹出被调用对象已与其客户端断开连接解决方法
  15. 快收下这枚 Scrapy Requests 口味的爬虫“回魂丹”
  16. EXCEL-VBA:EXCEL的各种文件名获取
  17. emmc和MMC的区别
  18. 春季活动策划案例合集(共25份)
  19. 坐标反算c语言程序,测量坐标计算程序
  20. 学习数据分析,数据分析必备的技能有哪些

热门文章

  1. python匿名函数调用_(Python) 函数、匿名函数
  2. 达人评测 i7 13700和i7 12700选哪个
  3. 佳能(Canon)打印机初始化备忘录
  4. python天勤金叉编程代码大全_天勤终端数据解决方案
  5. 小说作者推荐:焦糖冬瓜合集
  6. 清北学堂 2017-10-01
  7. mysql 绿色版的安装过程和配置
  8. mysql 索引代码_mysql索引
  9. android 路由器 操作系统,小米全新路由器操作系统是什么?支持什么语言?
  10. ubuntu解压各种文件