如何解决秒杀的性能问题和超卖的讨论 及防止按钮多次点击

摘要:
抢购活动通常经历三个主要环节:预约、抢单和付款。其中,抢单是检验业务提供商抗压能力的最重要环节。抢单过程一般会带来两个问题:1、秒杀人的高并发和热在线人数从10w开始,这从头到尾都是对网站架构的考验。2.任何超卖商品都将有最大数量。如何避免成功下单购买商品的人数不超过商品的最大数量,是每个抢购活动都会面临的问题。2、 如何解决?第一

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

抢订单环节一般会带来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的处理性能,最终导致前端页面出现超时异常。

 

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

  I:  关闭死锁检测,提高并发处理性能。

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

  III:组提交,降低server和引擎的交互次数,降低IO消耗。

以上内容可以参考丁奇在DTCC2013上分享的《秒杀场景下MySQL的低效》一文。在文中所有优化都使用后,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:

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

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

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

 

解决方案4:

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

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

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

 

三、总结


 

1、前端三板斧【扩容】【限流】【静态化】

2、后端两条路【内存】+【排队】

防止按钮多次点击

var nn = 0;
var tipId;
//打印小票调自动打印
function OrderbyHands() {
    nn = 10;
    if (nn === 10) {
        startPrint();
        tipId = setInterval("changebtntxt()", 1000); //每隔1秒调用一次changebtntxt()方法
    }
}
function changebtntxt() {
    if (nn > 0) {
        var vv = " 打印小票 (" + nn + ")";
        $("#btnOrderPrint").prop("disabled", "disabled"); //使按钮不能被点击
        $("#btnOrderPrint").text(vv); //更改按钮上的文字
        nn--;
    }
    if (nn === 0) {
        console.info("tipId", tipId);
        $("#btnOrderPrint").removeAttr("disabled"); //使按钮能够被点击
        $("#btnOrderPrint").text(" 打印小票 "); //更改按钮上的文字
        clearInterval(tipId); //清除循环事件
    }
    
}

function startPrint() {
    var rows = grid_order.getSelecteds();
    var OrderIds = [];
    for (var i = 0; i < rows.length; i++) {
        OrderIds.push(rows[i]["OrderId"]);
    }
    if (OrderIds.length == 0) {
        modal.dangerAlert("请选择单据!");
        return;
    }
    if (OrderIds.length > 20) {
        modal.dangerAlert("一次勾选单据不能超过20单!");
        return;
    }

    var pendingRequests = {};
    jQuery.ajaxPrefilter(function (options, originalOptions, jqXHR) {
        var key = options.url;
        console.log(key);
        if (!pendingRequests[key]) {
            pendingRequests[key] = jqXHR;
        } else {
            jqXHR.abort(); //放弃后触发的提交
            //pendingRequests[key].abort(); // 放弃先触发的提交
        }
        var complete = options.complete;
        options.complete = function (jqXHR, textStatus) {
            pendingRequests[key] = null;
            if (jQuery.isFunction(complete)) {
                complete.apply(this, arguments);
            }
        };
    });
    $.ajax({
        type: "POST",
        url: "/Print/GetOrderByHands",
        datatype: "Json",
        data: { OrderIds: OrderIds },
        success: function (data) {
            if (data.IsSuccess) {
                orderPrintbyHands(data.Data.PrintOrders);
            } else {
                modal.dangerAlert(data.Message);
            }
        },
        error: function (data) {
            modal.dangerAlert(data.responseText);
        }
    });
}
  var clicktag = 0;  //标志
    $("#paywechat").click(function () {
        debugger;
        if (clicktag == 0) {  //判断标志
            clicktag = 1;   //进行标志,防止多次点击
            $("#paywechat").prop("disabled", "disabled"); //将input元素设置为disabled
            //进行标志,防止多次点击
            //定时器
            alert(1);
            //callpay(); 
            //定时器
            //setTimeout(function() {
            //    isClick = true;
            //    $("#paywechat").removeAttr("disabled");//去除input元素的disabled属性
            //}, 5000);//5秒内不能重复点击

            //setTimeout(function() {
            //    clicktag = 0;
            //    $("#paywechat").removeAttr("disabled");//去除input元素的disabled属性
            //}, 5000);  
            setTimeout(() => {
                clicktag = 0;
                $("#paywechat").removeAttr("disabled");//去除input元素的disabled属性
            }, 5000);

        }  
    });

参考

http://www.cnblogs.com/yc-755909659/p/3591758.html
http://www.hollischuang.com/archives/931

https://blog.csdn.net/weixin_40687883/article/details/81386127

https://blog.csdn.net/u014656173/article/details/52208169?utm_source=blogxgwz6

免责声明:文章转载自《如何解决秒杀的性能问题和超卖的讨论 及防止按钮多次点击》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ACM题集以及各种总结大全(转)DMA映射 dma_addr_t下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

商品库存的扣除过程,如何防止超卖?

在商品购买的过程中,库存的抵扣过程,一般操作如下: 1、select根据商品id查询商品的库存。 2、根据下单的数量,计算库存是否足够,如果存库不足则抛出库存不足的异常,如果库存足够,则减去扣除的库存得到最新的库存剩余值。 3、set设置最新的库存剩余值。 上述过程的伪代码如下: // 根据商品id获取商品剩余库存 select stock_remaing...

简单实现redis实现高并发下的抢购/秒杀功能(转)

简述 抢购/秒杀是如今很常见的一个应用场景,那么高并发竞争下如何解决超抢(或超卖库存不足为负数的问题)呢? 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数 这里我就只谈redis的解决方案 我们先来看以下php代码是否能正确解决超抢/卖的问题: <?ph...

订单减库存设计

$goods->query('update order set  = store- num where  store>=num and goodID = 12345'); $goods->query('update order set  = store- num where  store>=num and goodID = 123...