redis实现高并发下的抢购/秒杀功能

乎合      2022-02-17     469

关键词:

1, http://www.cnblogs.com/phpper/p/6716248.html

https://www.cnblogs.com/phpper/p/7085663.html

https://www.cnblogs.com/TankXiao/p/4045439.html

悲观锁(Pessimistic Lock)

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。

 

 

乐观锁(Optimistic Lock)

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。好吧,在此唠叨总结下这两个锁:

总结

  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

 

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

常规写法:

查询出对应商品的库存,看是否大于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操作是原子的,即使有很多用户同时到达,也是依次执行的。抢购的示例代码如下:

比如这里我先把库存放入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库存字段








































php和redis实现在高并发下的抢购及秒杀功能示例详解(代码片段)

...面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等。抢购、秒杀实现很简单,但是有些问题需要解决,主要针对两个问题:一、高并发对数据库产生的压力二、竞争状态下如何解决库存的正确减少("超卖"问题... 查看详情

高并发下的商城秒杀设计php+mysql+redis的实现

<?php/*高并发的秒杀设计php+mysql+redis*Author:lms<php7在qq.com>QQ:二一九二4238*转发请注明来源网址http://www.thinkunion.nethttps://blog.csdn.net/weixin_43932088正常的操作:点击拍下,判断有没有库存,有则拍成功& 查看详情

面试实战考核:设计一个高并发下的下单功能(代码片段)

功能需求:设计一个秒杀系统初始方案商品表设计:热销商品提供给用户秒杀,有初始库存。@EntitypublicclassSecKillGoodsimplementsSerializable@IdprivateStringid;/***剩余库存*/privateIntegerremainNum;/***秒杀商品名称*/privateStringgoodsName;秒杀订单表... 查看详情

javaredis实现抢购秒杀

2018.10.24今天研究了下抢购秒杀的功能实现网上查了一大堆用redis的最多。主要是通过redis的watchmulti事务来控制秒杀数量不超卖。这里说下自己的感受:不超卖的话那就要一个个的来减库存这样的话效率上会有点问题这里上下代码... 查看详情

数据存储redis第四章:高并发下实现分布式锁(代码片段)

直接上代码:大部分互联网公司实现分布式锁原理/***分布式锁底层实现原理*@return*/@GetMapping("distributedLock")publicObjectdistributedLock()StringlockKey="distributedLockKey";//给每个线程都设置一个唯一标识,避免出现程序执行的时间超过设置的... 查看详情

数据存储redis第四章:高并发下实现分布式锁(代码片段)

直接上代码:大部分互联网公司实现分布式锁原理/***分布式锁底层实现原理*@return*/@GetMapping("distributedLock")publicObjectdistributedLock()StringlockKey="distributedLockKey";//给每个线程都设置一个唯一标识,避免出现程序执行的时间超过设置的... 查看详情

高并发下秒杀商品,你必须知道的9个细节(代码片段)

前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出现在商城的促销活动中,指定了一定数量&#... 查看详情

高并发下秒杀商品,你必须知道的9个细节(代码片段)

大家好,我是苏三,又跟大家见面了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出... 查看详情

高并发下秒杀商品,这9个细节得知道(代码片段)

大家好,我是bigsai,又跟大家见面了。前言高并发下如何设计秒杀系统?这是一个高频面试题。这个问题看似简单,但是里面的水很深,它考查的是高并发场景下,从前端到后端多方面的知识。秒杀一般出... 查看详情

阿里最后一面,高并发下如何设计一个秒杀系统?

近年来,随着“双十一”购物节和抖音等直播平台带货的热潮,大批促销活动涌现,「秒杀」这个词也越来越频繁地出现在我们的生活里。除了那些头部的电商公司,某宝、某东,还有各种街、某说、某会、... 查看详情

秒杀微服务实现抢购代金券功能(代码片段)

...活动表订单表创建秒杀服务pom依赖配置文件关系型数据库实现代金券秒杀相关实体引入抢购代金券活动信息代金券订单信息Rest配置类全局异常处理添加代金券秒杀活动代金券活动实体代金券活动Mapper->SeckillVouchersMapper代金券活... 查看详情

redis解决秒杀微服务抢购代金券超卖和同一个用户多次抢购(代码片段)

...edisson问题解决之前的博客,我通过传统的数据库方式实现秒杀按照正常逻辑来走,通过压 查看详情

redis轻松实现秒杀系统(代码片段)

...成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理... 查看详情

redis轻松实现秒杀系统(代码片段)

...成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理... 查看详情

高并发下的系统设计(偏数据库设计)

高并发完毕数据库设计是要结合不同的应用场景的,本文主要涉及到一下问题:1、对数据库表的字段訪问比較均衡,业务导向明显(网上商城,多条业务线);2、对数据库表的字段訪问比較均衡,业务导向不明显(对单一应用... 查看详情

高并发下缓存失效问题及解决方案

缓存穿透介绍:当查询一个不存在的数据,此时缓存是不命中的,就会去查询db,这将导致每次查询这个不存在的数据都要去访问db,缓存就没有意义了。如果不怀好意的人利用不存在的数据进行攻击,可能导致数据库崩溃解决... 查看详情

redis事务系列之三redis乐观锁实现秒杀(代码片段)

...看。Redis事务系列之一Redis事务详解Redis事务系列之二Redis实现乐观锁本章我们继续在上面的基础上用乐观锁实现秒杀。二、秒杀的实现我们先来设置一个场景,假设有50个商品,1000个人抢购。那么最终会有50个人买到商品... 查看详情

java用redis如何处理电商平台,秒杀抢购超卖

这是在一个方法调用下面代码的部分if(!this.checkSoldCountByRedisDate(key,limitCount,buyCount,endDate))//标注10: thrownewServiceException("您购买的商品【"+commodityTitle+"】,数量已达到活动限购量"); 2、下面是 查看详情