Skip to content

资源抢购

一、背景

资源抢购问题,如优惠券、红包等抢购问题。在高并发的情况下会引发库存超卖、红包超出个数等问题

二、优惠券抢购

三、超卖

在修改数据库的时候会存在库存扣减超卖,出现负数的情况。如何保证优惠券不超卖:

通过数据库乐观锁。下面是三个乐观锁方案

1)update product set stock=stock-1 where id = 1 and stock>0
id是主键索引的前提下,如果每次只是减少1个库存,则可以采用上面的方式,只做数据安全
校验,可以有效减库存,性能更高,避免大量无用sql,只要有库存就也可以操作成功.
场景:高并发场景下的取号器,优惠券发放扣减库存等

2)update product set stock=stock-1 where stock=#{原先查询的库存}  and id = 1 and stock>0
使用业务自身的条件做为乐观锁,但是存在ABA问题,对比方案三的好处是不用增加version版本字段。如果只是扣减库存且不在意ABA问题时,则可以采用上面的方式,但业务性能相对方案一就差了点,因为库存变动后sql就会无效

3)update product set stock=stock-1,versioin = version+1 where  id = 1 and stock>0 and version=#{原先查询的版本号}
增加版本号主要是为了解决ABA问题,数据读取后,更新前数据被别人篡改过,version只能做递增
场景:商品秒杀、优惠券方法,需要记录库存操作前后的业务

四、单用户多抢

同一个用户抢到了多张相同的优惠券,导致了黑客通过脚本抢用优惠券。

解决方案:分布式锁

在进行抢购优惠券的时候进行一个加锁,通过分布式锁控制优惠券资源的抢占。这个时候分布式锁的控制颗粒度只需要细到用户上,不用对整个优惠券的进行加锁。因为主要是解决相同用户抢购多个优惠券问题。

//如redis锁的key
String lockKey = String.format(CacheKey.PROMOTION_COUPON_LOCK_KEY,couponId,Context.getTokenContext().getUserId());

五、抢红包

抢红包功能主要有两个步骤,先是发红包,然后再抢红包。这个时候如何放在红包超发

解决方案1:

  • 发红包:在发红包的时候对红包的金额和个数进行计算,然后插入到redis或者数据库当中。

  • 抢红包:在抢红包的时候通过对红包进行一个判断,通过lua脚本,判断红包是否被抢完,再进行获取。入库操作可以异步进行,使用异步线程或者mq(根据实际需求决定是否同步入库)