zoukankan      html  css  js  c++  java
  • 商城商品超卖处理

    首先环境介绍下:商城商品可能存在几个端(PC、APP),其次每个端对应的服务端又可能做了负载均衡(即也有多个服务端)。

    要实现的目标和功能:保证商品不会出现超卖的情况。超卖商品后,无法对商品进行发货,是一种不负责任的行为。

    方案实现讨论流程

    “要实现不超卖,首先商品库存的扣减不能使用框架进行更新,因为框架是设置值,如果在这段时间,又有人购买了,则商品库存必然会出现问题。要采用手写SQL方式。并且sql中还要判断是否大于等于指定的购买量。”

    UPDATE `SKU_Info`  SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000;
    

    “要实现不超卖,我们可以对方法加上同步锁,这样可以解决”。

    “方法加上同步锁后,用户下单将会出现排队的情况,性能有问题。”

    “那我们可以实现对同一商品进行加锁,这样可以解决购买不同商品不会相互阻塞。如果有包含关系,也应该加锁。比如A用户购买商品1和商品2,B用户购买商品1,因为他们都有商品1,则应该加锁。”

    “你这个方案应该可以解决问题,采用分布式锁的方式可以解决,我们可以使用redis来做。”

    “是的,确实可以解决问题,并且多个服务端也不存在问题了,就这么干。”

    “我们可以对订单中的所有商品的sku值进行排序,拼接成一个skuId值,然后MD5的值作为key,其它订单进来方法时,按同样的操作进行检测是否正在下单,如果是,则等待。”

    “你这种方案忽略了商品不同的情况,就比如上面的例子中,A购买商品1和商品2,B购买商品1,那么他们的key是不同的,因而达不到效果。”

    “我们可以对每个商品sku的id定义个锁,这样每次购买时,我们针对每个商品进行检测,这样就可以了,绝对能够保证同步。”

    “这种方法可行,不过还是存在一个问题,服务端与redis的连接次数会比较多,如果一个用户下单商品种类较多,那么仍然会比较慢,但这确实不失为一个好的方案。”

    “既然这个方案仍然有可能有问题,那么还有没有其它的方案。”

    “数据库本身是有锁的,可以实现锁同步的问题,那么有没有办法使用到数据库的锁来解决这个问题?”

    “对呀,我们可以写SQL语句去循环扣减库存,最后判断数据库影响行数与商品种类是否匹配?如果不匹配,则是扣减失败,进行还原,如果匹配,则扣减成功!”

    “经过测试,我们用的MySQL不支持这种方案,里面需要用到if判断,而if判断必须要在存储过程中才能使用。”

    “那我们可以使用存储过程来做。代码如下”

    DELIMITER $$
    USE anke_skucenter$$
    CREATE PROCEDURE minusSkuNum()
    BEGIN
    SET AUTOCOMMIT=0;
    START TRANSACTION;
    UPDATE SKU_Info SET skuNum=skuNum-100 WHERE id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100;
     IF(SELECT ROW_COUNT()<=0)THEN
    	ROLLBACK;
    	END IF;
    UPDATE `SKU_Info`  SET skuNum=skuNum-1000 WHERE id='00293cb7-d8cf-4470-a66d-bb45ca2b130000293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000;
    IF(SELECT ROW_COUNT()<=0)THEN
    	ROLLBACK;
    END IF;
    COMMIT;
    SET AUTOCOMMIT=1;
    END$$
    

     “这个是初步的存储过程,仍然需要将update语句变更为循环,改变传入参数为商品id和数量,有谁会写?”

    “额,目前大家都不会写,并且这个循环看上去也挺复杂的。”

    “那么我们能不能在mybatis中获取多条更新语句的影响行数?”

    “不能,没有任何框架支持,并且mysql本身就不支持,要不然也不会需要存储过程了。”

    “既然多条SQL不行,能不能放到一条SQL中去做更新呢?”

    “先baidu下”

    “哈哈,找到了,我们查询的时候有时候回用到case when,那么我们更新的时候是否可以使用这个呢?尝试代码如下:”

    update SKU_Info set skuNum=skuNum-(case when id='0031394c-8058-49f5-9ba9-f971480ac2f2'  then 100 
    when id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' then 1000 end)
    where (id='0031394c-8058-49f5-9ba9-f971480ac2f2' AND skuNum>=100) 
    or (id='00293cb7-d8cf-4470-a66d-bb45ca2b1300' AND skuNum>=1000);
    

     “经过测试,该段代码执行正常,并且能够正常返回需要的影响行数。”

    “这个循环的SQL编写在mybatis中不难,那么判断最后扣减结果与商品种类不同时,如何进行补偿呢?”

    “这个可以使用@Transactional,我们在方法上加此注解,在方法内部判断,如果不同,我们就抛出一个自定义异常,这样就会自动进行回滚了。”

    “测试一下”

    “经过几轮测试,确实可行,就这样做。”

    “具体实施为:先生成订单,然后进行扣减,如果捕获到扣减失败的自定义异常,则对生成的订单执行删除标记。但存在一个问题,就是标记订单为删除状态失败的情况,这个订单仍然存在,也是超卖了。”

    “可以调整下,改为先进行扣减,扣减成功再生成订单,这样可以避免此问题。”

    “嗯,此方法可以解决超卖问题,可能会存在商品扣减成功,但订单未生成的情况。”

    “这种问题会存在,但比超卖要好很多。”

    “嗯嗯”

    “嗯嗯”

  • 相关阅读:
    【转载】分布式环境Raft一致性共识算法解读
    从码农到工程师:只要做到这6点
    产品思维的修炼–技术的必修课
    工具篇
    安全测试
    测试体会
    测试题目
    软件测试工具
    常见的性能测试方法
    性能测试在软件测试的周期位置
  • 原文地址:https://www.cnblogs.com/maomao999/p/9223721.html
Copyright © 2011-2022 走看看