设为首页 收藏本站
查看: 928|回复: 0

[经验分享] redis实现高并发下的抢购/秒杀功能

[复制链接]

尚未签到

发表于 2017-12-21 07:01:05 | 显示全部楼层 |阅读模式
  之前写过一篇文章,高并发的解决思路(点此进入查看),今天再次抽空整理下实际场景中的具体代码逻辑实现吧:
  抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢?
  常规写法:
  查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数
  这里我就只谈redis的解决方案吧...
  我们先来看以下代码(这里我以laravel为例吧)是否能正确解决超抢/卖的问题:
  

<?php$num = 10;   //系统库存量  $user_id =  \Session::get('user_id');//当前抢购用户id
  $len = \Redis::llen('order:1');  //检查库存,order:1 定义为健名
  if($len >= $num)
  return '已经抢光了哦';
  

  
$result = \Redis::lpush('order:1',$user_id);  //把抢到的用户存入到列表中
  
if($result)
  
  return '恭喜您!抢到了哦';
  

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

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

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

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

  
 //echo \Redis::llen('goods_store:1');//未抢购之前这里就是10了
  

  好吧,抢购时间到了:
  

/* 模拟抢购操作,抢购前判断redis队列库存量 */  $count=\Redis::lpop('goods_store:1');//lpop是移除并返回列表的第一个元素。
  
if(!$count)
  return '已经抢光了哦';
  

/* 下面处理抢购成功流程 */  

\DB::table('goods')->decrement('num', 1);//减少num库存字段  

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

$user_id =  \Session::get('user_id');//当前抢购用户id  
/* 模拟抢购操作,抢购前判断redis队列库存量 */
  
$count=\Redis::lpop('goods_store:1');
  
if(!$count)
  
  return '已经抢光了哦';
  

  
$result = \Redis::lpush('order:1',$user_id);
  
if($result)
  
  return '恭喜您!抢到了哦';
  

  

  为了检测实际效果,我使用jmeter工具模拟100、200、1000个用户并发进行抢购,经过大量的测试,最终抢购成功的用户始终为10,没有出现“超抢/超卖”。
  上面只是简单模拟高并发下的抢购思路,真实场景要比这复杂很多,比如双11活动远远比这更复杂多啦,很多注意的地方如抢购活动页面做成静态的,通过ajax调用接口
  再如上面的会导致一个用户抢多个,思路:
  需要一个排队队列(比如:queue:1,以user_id为值的列表)和抢购结果队列(比如:order:1,以user_id为值的列表)及库存队列(比如上面的goods_store:1)。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在则已抢购,否则未抢购,接着执行库存减1,写入数据库,将此user_id用户同时也进入结果队列。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-426297-1-1.html 上篇帖子: Redis事务原理分析 下篇帖子: Redis迁移的两种方式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表