【Python】电商网站如何解决秒杀高并发超卖问题

版权声明:尊重原作者,转载请在文章头部注明网址。 https://blog.csdn.net/u013034226/article/details/85236513

搜集了一些解决秒杀、高并发、超卖、问题的思路,仅供参考!

抢订单环节一般会带来2个问题:

  1、高并发

  比较火热的秒杀在线人数都是10w起的,如此之高的在线人数对于网站架构从前到后都是一种考验。

  2、超卖

  任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难题。

1、前端

  面对高并发的抢购活动,前端常用的三板斧是【扩容】【静态化】【限流】

  A:扩容

  加机器,这是最简单的方法,通过增加前端池的整体承载量来抗峰值。

  B:静态化

  将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。

  C:限流

  一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。

  或者活动入口的时候增加游戏或者问题环节进行消峰操作。

  D:有损服务

  最后一招,在接近前端池承载能力的水位上限的时候,随机拒绝部分请求来保护活动整体的可用性。

2、后端

  那么后端的数据库在高并发和超卖下会遇到什么问题呢?主要会有如下3个问题:(主要讨论写的问题,读的问题通过增加cache可以很容易的解决)

  I: 首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。

  II: 其次,超卖的根结在于减库存操作是一个事务操作,需要先select,然后insert,最后update -1。最后这个-1操作是不能出现负数的,但是当多用户在有库存的情况下并发操作,出现负数这是无法避免的。

  III:最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。

解决方案1:

  将存库从MySQL前移到Redis中,所有的写操作放到内存中,由于Redis中不存在锁故不会出现互相等待,并且由于Redis的写性能和读性能都远高于MySQL,这就解决了高并发下的性能问题。然后通过队列等异步手段,将变化的数据异步写入到DB中。

  优点:解决性能问题

  缺点:没有解决超卖问题,同时由于异步写入DB,存在某一时刻DB和Redis中数据不一致的风险。

解决方案2:

  引入队列,然后将所有写DB操作在单队列中排队,完全串行处理。当达到库存阀值的时候就不在消费队列,并关闭购买功能。这就解决了超卖问题。

  优点:解决超卖问题,略微提升性能。

  缺点:性能受限于队列处理机处理性能和DB的写入性能中最短的那个,另外多商品同时抢购的时候需要准备多条队列。

解决方案3:

  将写操作前移到Memcached中,同时利用Memcached的轻量级的锁机制CAS来实现减库存操作。

  优点:读写在内存中,操作性能快,引入轻量级锁之后可以保证同一时刻只有一个写入成功,解决减库存问题。

  缺点:没有实测,基于CAS的特性不知道高并发下是否会出现大量更新失败?不过加锁之后肯定对并发性能会有影响。

解决方案4:

  将提交操作变成两段式,先申请后确认。然后利用Redis的原子自增操作(相比较MySQL的自增来说没有空洞),同时利用Redis的事务特性来发号,保证拿到小于等于库存阀值的号的人都可以成功提交订单。然后数据异步更新到DB中。

  优点:解决超卖问题,库存读写都在内存中,故同时解决性能问题。

  缺点:由于异步写入DB,可能存在数据不一致。另可能存在少买,也就是如果拿到号的人不真正下订单,可能库存减为0,但是订单数并没有达到库存阀值。

http://www.cnblogs.com/billyxp/p/3701124.html


使用mysql的事务加排他锁来解决,首先我们选择数据库的存储引擎为innoDB,使用的是排他锁实现的,刚开始的时候我们测试了下共享锁,发现还是会出现超卖的现象。有个问题是,当我们进行高并发测试时,对数据库的性能影响很大,导致数据库的压力很大。

使用文件锁实现。当用户抢到一件促销商品后先触发文件锁,防止其他用户进入,该用户抢到促销品后再解开文件锁,放其他用户进行操作。这样可以解决超卖的问题,但是会导致文件得I/O开销很大。

使用redis的setnx来实现锁机制。但是并发大的情况下,锁的争夺会变多,导致响应越来越慢。

redis的队列来实现。将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。这个操作起来很方便,而且效率极高

https://blog.csdn.net/b1303110335/article/details/81365347


一般电子商务网站都会遇到如团购、秒杀、特价之类的活动,而这样的活动有一个共同的特点就是访问量激增、上千甚至上万人抢购一个商品。然而,作为活动商品,库存肯定是很有限的,如何控制库存不让出现超买,以防止造成不必要的损失是众多电子商务网站程序员头疼的问题,这同时也是最基本的问题。

从技术方面剖析,很多人肯定会想到事务,但是事务是控制库存超卖的必要条件,但不是充分必要条件。

数据库的访问其实就是对磁盘文件的访问,数据库中的表其实就是保存在磁盘上的一个个文件,甚至一个文件包含了多张表。例如由于高并发,当前有三个用户a、b、c三个用户进入到了这个事务中,这个时候会产生一个共享锁,所以在select的时候,这三个用户查到的库存数量都是4个,同时还要注意,mysql innodb查到的结果是有版本控制的,再其他用户更新没有commit之前(也就是没有产生新版本之前),当前用户查到的结果依然是旧版本;

      然后是update,假如这三个用户同时到达update这里,这个时候update更新语句会把并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁,在当前这个update语句commit之前,其他用户等待执行,commit后,生成新的版本;这样执行完后,库存肯定为负数了。
行级锁:for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。select * from table_name where id =1 for update ;

注:当选中某一个行的时候,如果是通过主键id选中的。那么这个时候是行级锁。 
其他的行还是可以直接insert 或者update的。如果是通过其他的方式选中行,或者选中的条件不明确包含主键。这个时候会锁表。其他的事务对该表的任意一行记录都无法进行插入或者更新操作。只能读取。

1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的
必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败再将数据递增1(加锁/解锁),否则表示交易成功。
当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。

2、这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。
把你要卖出的商品比如10个商品放到缓存中;然后在memcache里设置一个计数器来记录请求数,这个请求书你可以以你要秒杀卖出的商品数为基数,比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。

3、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。

这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。
乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。
悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。
除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。

4、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

5、实际应用中,并不是让mysql去直面大并发读写,会借助“外力”,比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。

https://blog.csdn.net/qq_22075041/article/details/80075334


python 提供了2种方法解决该问题的问题:1,悲观锁;2,乐观锁

        悲观锁:在查询商品储存的时候加锁 select_for_update()  在发生事务的commit或者是事务的rollback时,自动释放该锁,这样其他用户就可以接着查询该商品。

        乐观锁:乐观锁不是真正的锁,在创建订单之前查询商品的库存,在创建订单详情表前,update更新查询数据,如果两次查询的库存量一样就创建详情表,并减去库存,否则,循环三次,如果都不一样,就发生rollback。

        使用场景:并发量高的时候使用悲观锁,缺点:加锁消耗资源

                       并发量低的时候使用乐观锁,缺点:乐观锁循环耗费时间。
--------------------- 

原文:https://blog.csdn.net/huangyali_python/article/details/79511654 


mysql数据库,多个线程并发更新同条数剧时,会自动并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁。那么我只要设计这个库存字段unsigned不让它可能为负数。不就好了吗?当更新结果为负数的时候,mysql会自动抛出异常,这时候我服务端catch住异常,所有数据库操作回滚,提示无库存,从而避免库存超卖的情况。
    当然我没有考虑性能方面,不知道我这个想法是否正确?

给你举个例子 
A  读取到P 当前 有100 个。 扣减一个库存的动作完成之前 ,B 读取了P 数据, 这时候P 的库存仍然是100 , 而这时候A 扣减动作完成,那么当前P 还有99个, 而B 拿到的却是100个,
如果依靠数据库的隔离级别的话,大大的降低了数据处理能力,楼主说的排它锁在抢购的业务场景下,极容易发生死锁。
而抢购对用户体验有很高的要求,需要根据不同业务场景设计出合理的流程。

乐观锁:是在表中加个版本字段,事务启动,更新某条数据之前,去获取它的版本,更新数据。之后再次获取版本,比对两个版本值,一样则提交事务,不一致则事务回滚

用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题

稍微有点流量的秒杀场景都不适合把请求直接压到数据库上,或者说不适合直接依赖磁盘的读写能力,用户稍微发力一下就把数据库连接打满了。

回到这个问题本身,业务上秒杀一般都有一个预栈的设置,是大于实际库存的虚拟库存,而这个库存内的商品真正是真正可秒杀商品。在秒杀活动中就算因为种种原因约束被一枪捅穿,活动商品的发货依然是可控的。另外这个预栈设计也是缓存设计的关键,围绕它可以做业务和技术两方面的优化。

一般线上的控制都有几层的。 一层的控制谁也没法保证。  比如我们新增操作还有分布式锁。 这些都是处理方案
队列是减缓 对数据库的压力, 乐观锁和唯一索引等是防止意外。

1. 所谓秒杀问题,就是性能的问题。如果不考虑性能,压根就不存在秒杀场景,就是一个普通的销售场景。
2. 超卖是一个业务问题,而不是一个技术问题,出现超卖只能说业务设计出现了漏洞。
3. 技术上有的是手段能完全杜绝超卖:严格事务控制,高一致性方案。
4. 业务架构上有很多机制能防止超卖,只要最后结果没有超卖就行了。

猜你喜欢

转载自blog.csdn.net/u013034226/article/details/85236513