高并发引起的库存超发解决方案

库存设计:设置锁定库存和总库存,当用户下单未支付时锁定库存,支付成功时释放锁定库存并扣减总库存,当30分钟用户还未支付,此时释放锁定库存不扣减总库存。

一、库存超发原因:

下单流程:

当库存为1时,两个用户同时下单,查询库存时都有库存,都通过了库存数量校验,下单成功库存为-1。

二、解决方案:

(1)悲观锁
实现方式:查询时添加更新锁。

实现原理:使当前线程持有数据库记录行更新锁,其它线程被挂起,直到当前线程执行完释放锁,其他线程才能获取行更新锁,这样就防止了超发现象。

select stock from t_stock where id=1 for update

 

(2)乐观锁

实现方式:添加版本号。

实现原理:先获取当前版本号,执行完业务流程进行库存更新时,判断当前线程持有的版本号是否与数据库中的版本号相同,如一致则更新库存并增加版本号。乐观锁是一种不会阻塞其它线程并发的机制,它不会使用数据库的锁进行实现

缺陷:库存充足,并发的用户无法下单

解决方法:

乐观锁重入机制:

1)使用时间戳执行重入,比如在100毫秒内重复执行。当因为版本号更行失败后,会尝试重新下单,但是会进行时间戳判断,如果在100毫秒内,就继续,否则就判定失败。

2)限制重复次数执行重入

(3)redis使用lua脚本实现

实现原理:lua语言的原子性

-- 获取当前库存
local stock = tonumber(redis.call('hget', productId, 'stock'))

-- 如果购买量小于库存,返回0
if stock < quantity then return 0 end

-- 减少库存
stock = stock - 1

-- 保存当前库存
redis.call('hset', productId, 'stock', tostring(stock))

-- 返回成功
return 1

  使用redis实现代码:

@Autowired
private RedisTemplate redisTemplate;

//lua脚本
String script = "local stock = tonumber(redis.call('hget', 'product', KEYS[1]) \n"
        +"if stock < ARGV[1] then return 0 end \n"
        +"stock = stock - 1 \n"
        +"redis.call('hset', 'product', KEYS[1], tostring(stock)) \n"
        +"return 1 \n";

//在缓存Lua脚本后,使用该变量保存Redis返回的32位的SHAl编码,使用它去执行缓存的Lua脚本
String  shal  =  null ;

@Override
public Long deductionStockByRedis(Integer productId, Integer qunatity )  {
    Long  result  =  null ;
    //获取底层Redis操作对象
    Jedis jedis = (Jedis)redisTemplate.getConnectionFactory().getConnection().getNativeConnection();
    Integer result = null;
    try  {
            //如果脚本没有加载过,那么进行加载,这样就会返回一个shal编码
            if  (shal == null )
                  shal =jedis.scriptLoad(script) ;
            //执行脚本,返回结果
            List<String> key= new ArrayList<String>();
            key.add(productId.toString());
            List<String> avge = new ArrayList<String>();
            avge.add(qunatity.toString());
            result = (Integer)jedis.evalsha(shal, key, avge );   
          }finally{
             //确保jedisl顺利关闭
            if  (jedis  !=  null  &&  jedis . isConnected (}}  {
                    jedis . close(} ;
          }
          return result;
    }

(4)根据数据库更新时会自动加上行级锁来实现防止超发

<update id="lockStock" parameterType="com.macro.mall.request.CartPromotionItem">
  		UPDATE pms_sku_stock
  		SET
  			lock_stock = CASE id 
  			<foreach collection="cartPromotionItemList" item="promotionItem">
				WHEN #{promotionItem.productSkuId} THEN lock_stock + #{promotionItem.quantity} + #{promotionItem.restrictQuantity}
			</foreach>
			END
  		WHERE
  			( (stock -lock_stock) >0 )
			AND<![CDATA[(stock-lock_stock) >=]]> CASE id 
  			<foreach collection="cartPromotionItemList" item="promotionItem">
				WHEN #{promotionItem.productSkuId} THEN (#{promotionItem.quantity} + #{promotionItem.restrictQuantity})
			</foreach>
			END
			AND id IN
			<foreach collection="cartPromotionItemList" item="promotionItem" separator="," open="(" close=")">
	           #{promotionItem.productSkuId}
	        </foreach>
  </update>

 

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值