之前写过一篇文章,高并发的解决思路(点此进入查看),今天再次抽空整理下实际场景中的具体代码逻辑实现吧:
抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢?

常规写法:

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

这里我就只谈redis的解决方案吧...
我们先来看以下代码(这里我以laravel为例吧)是否能正确解决超抢/卖的问题:

  1. <?php
  2.  
  3. $num = 10; //系统库存量
  4. $user_id = \Session::get('user_id');//当前抢购用户id
  5. $len = \Redis::llen('order:1'); //检查库存,order:1 定义为健名
  6. if($len >= $num)
  7.   return '已经抢光了哦';
  8.  
  9. $result = \Redis::lpush('order:1',$user_id); //把抢到的用户存入到列表中
  10. if($result)
  11.   return '恭喜您!抢到了哦';

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

  1. $len = \Redis::llen('order:1'); //检查库存,order:1 定义为健名
  2. if($len >= $num)
  3.   return '已经抢光了哦';

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

  1. $num=10; //库存
  2. $len=\Redis::llen('goods_store:1'); //检查库存,goods_store:1 定义为健名
  3. $count = $num-$len; //实际库存-被抢购的库存 = 剩余可用库存
  4. for($i=0;$i<$count;$i++)
  5.   \Redis::lpush('goods_store:1',1);//往goods_store列表中,未抢购之前这里应该是默认滴push10个库存数了
  6.  
  7.  //echo \Redis::llen('goods_store:1');//未抢购之前这里就是10了

好吧,抢购时间到了:

  1. /* 模拟抢购操作,抢购前判断redis队列库存量 */
  2. $count=\Redis::lpop('goods_store:1');//lpop是移除并返回列表的第一个元素。
  3. if(!$count)
  4. return '已经抢光了哦';
  1. /* 下面处理抢购成功流程 */
  1. \DB::table('goods')->decrement('num', 1);//减少num库存字段

用户抢购成功后,上面的我们也可以稍微优化下,比如我们可用将用户ID存入了order:1列表中。接下来我们可以引导这些用户去完成订单的其他步骤,到这里才涉及到与数据库的交互。最终只有很少的人走到这一步吧,也就解决的数据库的压力问题。
我们再改下上面的代码:

  1. $user_id = \Session::get('user_id');//当前抢购用户id
  2. /* 模拟抢购操作,抢购前判断redis队列库存量 */
  3. $count=\Redis::lpop('goods_store:1');
  4. if(!$count)
  5.   return '已经抢光了哦';
  6.  
  7. $result = \Redis::lpush('order:1',$user_id);
  8. if($result)
  9.   return '恭喜您!抢到了哦';

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

上面只是简单模拟高并发下的抢购思路,真实场景要比这复杂很多,比如双11活动远远比这更复杂多啦,很多注意的地方如抢购活动页面做成静态的,通过ajax调用接口
再如上面的会导致一个用户抢多个,思路:
需要一个排队队列(比如:queue:1,以user_id为值的列表)和抢购结果队列(比如:order:1,以user_id为值的列表)及库存队列(比如上面的goods_store:1)。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在则已抢购,否则未抢购,接着执行库存减1,写入数据库,将此user_id用户同时也进入结果队列。

redis实现高并发下的抢购/秒杀功能的更多相关文章

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

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

  2. (高级篇)php结合redis实现高并发下的抢购、秒杀功能

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

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

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

  4. 【转】php结合redis实现高并发下的抢购、秒杀功能

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

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

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

  6. Redis实现高并发下的抢购、秒杀功能

    博主最近在项目中遇到了抢购问题!现在分享下.抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖" ...

  7. php结合redis实现高并发下的抢购、秒杀功能【转】

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

  8. php结合redis实现高并发下的抢购、秒杀功能 (转)

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

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

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

随机推荐

  1. access纯jdbc连接

    Class.forName("com.hxtt.sql.access.AccessDriver"); String url = "jdbc:Access:///c:/a/ ...

  2. CSS3中很容易混淆的transform,translate,transition。如何去区分,以及综合写法。

    属性 含义     transition(过渡) 用于设置元素的样式过度,和animation有着类似的效果,但细节上有很大的不同 transform(变形) 用于元素进行旋转.缩放.移动或倾斜,和设 ...

  3. java.lang.OutOfMemoryError: PermGen space (jvm内存泄漏解决办法)

    2.在myeclipse根目录 打开myeclipse.ini 3.在myeclipse中配置内存

  4. Metasploit Framework(8)后渗透测试(一)

    文章的格式也许不是很好看,也没有什么合理的顺序 完全是想到什么写一些什么,但各个方面都涵盖到了 能耐下心看的朋友欢迎一起学习,大牛和杠精们请绕道 使用场景: Kali机器IP:192.168.163. ...

  5. Linux 下 pushd,popd,cd- 用法

    一,为何要使用这几个命令? 可能大家会有疑问,为何要使用这几个命令,   难道用cd不就可以切换目录了吗?   没错,使用cd就可以切换到需要访问的目录,   但是有时会是一个路径很长,层次很多的目录 ...

  6. JAVA中的Random()函数

    Java中存在着两种Random函数: 一.java.lang.Math.Random; 调用这个Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范 ...

  7. websocket ----简介,以及demo

    #导报 from dwebsocket.decorators import accept_websocket WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户 ...

  8. Http状态信息

    一.HTTP协议1.简介:http超文本传输协议,基于请求与响应模式的,无状态的,应用层的协议.绝大读书的web开发都是建立在http协议之上的.2.http工作过程:当请求一个超链接时,http就开 ...

  9. Zookeeper-watcher机制源码分析(一)

    Watcher的基本流程 ZooKeeper 的 Watcher 机制,总的来说可以分为三个过程:客户端注册 Watcher.服务器处理 Watcher 和客户端回调 Watcher 客户端注册wat ...

  10. Linux编程 18 安装软件程序(yum工具对软件包安装,删除,更新介绍)

    一.   概述 本篇介绍在linux上见到的各种包管理系统(package management system,PMS)用来进行软件安装,管理,删除的命令行工具.PMS是利用一个数据库来记录各种相关内 ...