抢购是如今很常见的一个应用场景,主要需要解决的问题有两个:

1 高并发对数据库产生的压力

2 竞争状态下如何解决库存的正确减少(“超卖”问题)

对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis。重点在于第二个问题,我们看看下面一种常规的实现代码:

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操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:

//抢购

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,没有出现“超卖”。

转载至链接:https://my.oschina.net/chinaxy/blog/1829233

java 队列 抢购_使用Redis实现抢购的一种思路(list队列实现)相关推荐

  1. [Redis]处理定时任务的2种思路

    用redis完成类似 at 命令的功能,例如订单24小时后没有支付自动关闭,定时发邮件,主要说下任务生成之后怎么触发消费. 使用 有序集合 思路: 使用sorted Sets的自动排序, key 为任 ...

  2. java 缓存分页_基于redis做缓存分页

    在实际业务中我们会将一些热数据缓存到redis里面,这时候数据量比较大的话,我们就要对这些热数据进行分页,分页的方式有2种: 第一:从redis拿出所有数据后,再做内存分页(不推荐),热点数据小的时候 ...

  3. redis java 发布订阅_【Redis学习系列】Redis发布订阅

    Redis 发布订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息. Redis 客户端可以订阅任意数量的频道. 下图展示了 ...

  4. java c3p0 配置文件_【c3p0】 C3P0的三种配置方式以及基本配置项详解

    数据库连接池C3P0框架是个非常优异的开源jar,高性能的管理着数据源,这里只讨论程序本身负责数据源,不讨论容器管理. ---------------------------------------- ...

  5. java 延迟实例化_延迟初始化Spring Bean的几种方式

    XML 配置: Java 注解:@Lazy(true) Spring 中默认是非延迟加载Bean的,也就是提前把Bean初始化好,用的时候直接用. 优点是运行的时候比较快(提前初始化了,直接用). 缺 ...

  6. java 定义全局变量_都说变量有七八种,到底谁是 Java 的亲儿子

    点击蓝色"陈树义"关注我哟 网上罗列了很多关于变量的理解,良莠不齐,不知道哪些是对的,哪些是错的.所以笔者索性就这些博客和自己的理解写出这篇文章,如果有不对的地方,希望读者能够指正 ...

  7. java限频_单个用户及Ip请求频率限制思路(附java实现)

    > 我们熟悉的限流算法漏桶和令牌桶外,很多情况我们还需要考虑当个用户(ip)访问频率控制,避免被恶意调用.如果是开放平台限制一天调用多少次这种粗放的粒度相对好处理一些.如果需要更小时间粒度控制, ...

  8. java dictionary遍历_遍历 Dictionary,你会几种方式?

    一:背景 1. 讲故事 昨天在 StackOverflow 上看到一个很有趣的问题,说: 你会几种遍历字典的方式,然后跟帖就是各种奇葩的回答,挺有意思,马上就要国庆了,娱乐娱乐吧,说说这种挺无聊的问题

  9. java web 下拉列表_关于web中下拉列表的几种实现方法

    从数据库中获得数据List,将数据放到Request里面使用setAttribute("AList",AList) A中有2个属性(String id,String value) ...

最新文章

  1. 学python的好处-python有什么优势
  2. CNN卷积层图像和矩阵转换函数
  3. 349. Intersection of Two Arrays 两个数组的交集
  4. 晒一下MAC下终端颜色配置
  5. 这次来个BGP反射器的简单小实验
  6. jsp中使用cookie时报错……
  7. mysql教程为什么很多都用dos_在dos操作mysql基础教程详解
  8. Android线程机制——AsyncTask
  9. 关于子查询中的order by子句
  10. 如何获取Java用户输入?
  11. 手把手带你了解安卓Unity3D手游
  12. Java - PhantomJS + EChartsConvert实现ECharts图片保存到服务端
  13. CoppeliaSim(Vrep)动力学仿真入门设置
  14. 揭秘:客服如何创造价值和自我超越
  15. 优化命令之top——实时显示进程动态/查看进程信息
  16. JAVA-----乱码的处理 乱码的解决方法总结
  17. glusterfs搭建
  18. 如何快速修改SQLite生成的.db文件(SQLite)
  19. 怎么理解计算机中的 payload
  20. 数据库 视图基础概念

热门文章

  1. MS SQL数据库备份和恢复
  2. 27-什么是自旋锁?自旋的好处和后果是什么呢?
  3. 彻底销毁数据的4个方法
  4. 机械键盘到底茶轴好还是黑轴好呢?第一次用,需要注意什么?
  5. 《等着我吧,我会回来》 苏·西蒙诺夫
  6. Docker入门实战大全终极版
  7. 计算机等级考试二级office知识点总结,计算机等级考试二级office知识点总结[共9页].doc...
  8. import 与 import()
  9. heatmap.js来绘制热力图
  10. Spark 内存模型