• 关于处理高并发,防止库存超卖的问题


    一.问题描述:

    一般电子商务网站都会遇到如团购、秒杀、特价之类的活动,而这样的活动有一个共同的特点就是访问量激增、上千甚至上万人抢购一个商品。

    然而,作为活动商品,库存肯定是很有限的,如何控制库存不让出现超买,以防止造成不必要的损失是众多电子商务网站程序员头疼的问题,这同时也是最基本的问题。

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

    1、在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的。

    必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,

    先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。

    当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。

    2、这个肯定不能直接操作数据库的,会挂的。直接读库写库对数据库压力太大,要用缓存。

    把你要卖出的商品比如10个商品放到缓存中;然后在memcache里设置一个计数器来记录请求数,这个请求数你可以以你要秒杀卖出的商品数为基数,

    比如你想卖出10个商品,只允许100个请求进来。那当计数器达到100的时候,后面进来的就显示秒杀结束,这样可以减轻你的服务器的压力。

    然后根据这100个请求,先付款的先得后付款的提示商品以秒杀完。

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

    这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。

    (1)乐观锁:就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,

                  但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。

    (2)悲观锁:就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。

    除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,

    这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。

    4、不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,

    若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。

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

    二.秒杀带来了什么

    秒杀或抢购活动一般会经过【预约】【抢订单】【支付】这3个大环节,而其中【抢订单】这个环节是最考验业务提供方的抗压能力的。

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

    1、高并发

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

    2、超卖

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

    三.如何解决

    首先讨论技术解决方案

    1、前端

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

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

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

    (3)限流:一般都会采用IP级别的限流,即针对某一个IP,限制单位时间内发起请求数量。或者活动入口的时候增加游戏或者问题环节进行消峰操作。

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

    2、后端

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

    (1)首先MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,

             但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。

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

    (3)最后,当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,

             从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。

    针对上述问题,如何解决呢? 先看眼淘宝的高大上解决方案:

    (1)关闭死锁检测,提高并发处理性能。

    (2)修改源代码,将排队提到进入引擎层前,降低引擎层面的并发度。

    (3)组提交,降低server和引擎的交互次数,降低IO消耗。

    淘宝在所有优化都使用后,TPS在高并发下,从原始的150飙升到8.5w,提升近566倍,非常吓人!

    不过结合我们的实际,改源码这种高大上的解决方案显然有那么一点不切实际。于是小伙伴们需要讨论出一种适合我们实际情况的解决方案。以下就是我们讨论的解决方案:

    首先设定一个前提,为了防止超卖现象,所有减库存操作都需要进行一次减后检查,保证减完不能等于负数。(由于MySQL事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖)

    update number set x=x-1 where (x -1 ) >= 0;

    解决方案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,但是订单数并没有达到库存阀值。

  • 相关阅读:
    Airodump-ng——Description
    kali 2.0 — WIFI——commands
    国外整理的一套在线渗透测试资源合集
    A collection of android security related resources.
    cBPM
    cBPM-android
    CentOS7 安装 gcc-4.9.0
    install Android Studio 1.3 —— VM Acceleration on Linux
    08嵌入式—蔺小会—初创公司需要怎样的管理模式?
    Nodejs开发框架Express4.x开发手记
  • 原文地址:https://www.cnblogs.com/ZJOE80/p/5671567.html
Copyright © 2020-2023  润新知