抢购是如今很常见的一个应用场景,主要需要解决的问题
2017-10-23 19:55
656 查看
抢购是如今很常见的一个应用场景,主要需要解决的问题有两个:
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少(“超卖”问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。重点在于第二个问题,我们看看下面一种常规的实现代码:
如果代码正常运行,列表order:1中最多只能存储10个用户的id,因为库存只有10个。
然而,在使用Apache AB工具模拟很多用户并发请求时,最后发现order:1中总是超过10个用户,也就是出现了“超卖”。
问题就出在这一段代码:
在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过,这三个用户都能抢购成功。而实际上只有一件库存可以抢了。
在高并发下,很多不是问题的,都成了问题。要解决“超卖”问题,核心在于保证检查库存时的操作是依次执行的,形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。
我们需要使用Redis的原子操作来实现这个“单线程”。首先我们把库存存在goods:1这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅代表一件库存。抢购开始后,每到来一个用户,就从goods:1中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:
用户抢购成功后,我们将用户ID存入了order:1列表中。接下来我们可以引导这些用户去完成订单的其他步骤,这里才涉及到与数据库的交互。最终只有很少的人走到这一步,也就解决的数据库的压力问题。
为了检测实际效果,我使用Apache AB工具模拟10、20、1000个用户并发进行抢购,经过大量的测试,最终抢购成功的用户始终为10,没有出现“超卖”。
1 高并发对数据库产生的压力
2 竞争状态下如何解决库存的正确减少(“超卖”问题)
对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。重点在于第二个问题,我们看看下面一种常规的实现代码:
<?php require('predis/src/Autoloader.php'); $redis = new Predis\Client(array( 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '6379' )); //redis 登录 $redis->auth('123456'); //库存 $num = 10; //用户id $user_id = $_SESSION['user_id']; //检查库存 $len = $redis->llen('order:1'); if($len >= $num){ exit('已经抢光了'); } //把抢到的用户存入到列表中 $result = $redis->lpush('order:1',$user_id); if($result){ echo '抢到了'; } ?>
如果代码正常运行,列表order:1中最多只能存储10个用户的id,因为库存只有10个。
然而,在使用Apache AB工具模拟很多用户并发请求时,最后发现order:1中总是超过10个用户,也就是出现了“超卖”。
问题就出在这一段代码:
//检查库存 $len = $redis->llen('order:1'); if($len >= $num){ exit('已经抢光了'); }
在抢购进行到一定程度,假如现在已经有9个人抢购成功,又来了3个用户同时抢购,这时if条件将会被绕过,这三个用户都能抢购成功。而实际上只有一件库存可以抢了。
在高并发下,很多不是问题的,都成了问题。要解决“超卖”问题,核心在于保证检查库存时的操作是依次执行的,形象的说就是把“多线程”转成“单线程”。即使有很多用户同时到达,也是一个个检查并给与抢购资格,一旦库存抢尽,后面的用户就无法继续了。
我们需要使用Redis的原子操作来实现这个“单线程”。首先我们把库存存在goods:1这个列表中,假设有10件库存,就往列表中push10个数,这个数没有实际意义,仅仅代表一件库存。抢购开始后,每到来一个用户,就从goods:1中pop一个数,表示用户抢购成功。当列表为空时,表示已经被抢光了。因为列表的pop操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:
<?php //抢购 require('predis/src/Autoloader.php'); $redis = new Predis\Client(array( 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => '6379' )); $redis->auth('123456'); //用户ID $user_id = $_SESSION['user_id']; $check = $redis->lpop('goods:1'); if(!$check){ exit('抢光了'); } $result = $redis->lpush('order:1',$user_id); if($result){ echo '抢购成功'; } ?>
用户抢购成功后,我们将用户ID存入了order:1列表中。接下来我们可以引导这些用户去完成订单的其他步骤,这里才涉及到与数据库的交互。最终只有很少的人走到这一步,也就解决的数据库的压力问题。
为了检测实际效果,我使用Apache AB工具模拟10、20、1000个用户并发进行抢购,经过大量的测试,最终抢购成功的用户始终为10,没有出现“超卖”。
相关文章推荐
- JQuery问题集锦:使用jquery中的each函数时,需要break、continue的应用场景解决办法
- PHP的一个完整SMTP类(解决当mail()遇上邮件服务器需要验证时的问题) [转]
- AJAX应用解决一些常见问题
- spring 中配置log4j输出日志常见的一个小问题解决方法
- 一个分布式应用的常见的设计问题
- 在IE里应用XML的一个小例子:解决双下拉选单的连动问题。
- 问题需要反过来想,oracle连接问题的一个另外的解决办法。
- Apache配置--同一台机器上两个应用需要同时占用80端口的问题解决方法
- 一个常见REST应用场景的困惑和探究
- 创建并部署一个连接SQLAzure的WindowsAzure应用遇到的问题及解决办法
- AJAX应用解决一些常见问题
- 在使用Hibernate时,因为一个查询需要更多的表连接而要使用SQL来解决性能问题。然而返回的结果集中包含了没有映射的Entity类中的表字段,在这个SQL中还有使用如何将层次关系的父子结点显示为横行
- 解决一个非常小的常见的问题-安装软件configure时 error: C compiler cannot create executables
- AJAX应用解决一些常见问题
- 推荐一个 SQL2005 应用常见问题解答网站
- 一个需要达人解决的问题
- 关于一个应用分枝界限法解决最小顶点覆盖问题的程序说明
- PHP的一个完整SMTP类(解决邮件服务器需要验证时的问题)
- 实现对图的一个指定的操作或用图解决一个应用问题
- AJAX应用解决一些常见问题