电商系统中秒杀是一种常见的业务场景需求,其中核心设计之一就是如何扣减库存。本篇主要分享一些常见库存扣减技术方案,库存扣减设计选择并非一味追求性能更佳,更多的应该考虑根据实际情况来进行架构取舍。在商品购买的过程中,库存的抵扣过程通常包括以下步骤:
- 开启事务:在开始进行库存抵扣操作前,开启一个事务。
- 查询库存:根据商品ID,使用SELECT语句从库存表中查询该商品的当前库存数量。
- 检查库存是否足够:将查询到的库存数量与用户购买数量进行比较。如果库存数量大于或等于用户购买数量,则库存足够,可以继续下单。如果库存不足,需要采取相应的处理措施,例如提示用户库存不足或进行库存预订等。
- 扣减库存:如果库存足够,根据用户购买数量,使用UPDATE语句将库存表中对应商品的库存数量减去购买数量,得到最新的库存剩余值。
- 记录交易明细:在购买过程中,通常需要记录交易明细,例如生成订单记录或交易日志,以便后续查询和跟踪。
- 提交事务:以上操作通常会在一个事务中进行,确保操作的原子性。如果所有步骤都成功执行,则提交事务,库存扣减过程完成。如果在任何步骤中出现错误或异常,事务会回滚,恢复到操作前的状态,确保数据的完整性和一致性。
由于涉及到 SELECT后进行UPDATE,以上步骤中存在多事务并发时写覆盖的问题。
秒杀考虑的问题
1、超卖问题
假如备货只有100个,但是最终超卖了200,一般来讲秒杀系统的价格都比较低,如果超卖将严重影响公司的财产利益,因此首当其冲的就是解决商品的超卖问题。
2、高并发
秒杀具有时间短、并发量大的特点,秒杀持续时间只有几分钟,而一般公司都为了制造轰动效应,会以极低的价格来吸引用户,因此参与抢购的用户会非常的多。短时间内会有大量请求涌进来,后端如何防止并发过高造成缓存击穿或者失效,击垮数据库都是需要考虑的问题。
单台redis服务器可承受的QPS大概是4W左右,如果一个秒杀吸引的用户量足够多的话,单QPS可能达到几十万,单体redis还是不足以支撑如此巨大的请求量。缓存会被击穿,直接渗透到DB,从而击垮mysql.后台会将会大量报错。
秒杀是一个读多写少的场景,使用redis做缓存再合适不过。不过考虑到缓存击穿问题,我们应该构建redis集群,采用哨兵模式,可以提升redis的性能和可用性。
3、接口防刷
现在的秒杀大多都会出来针对秒杀对应的软件,这类软件会模拟不断向后台服务器发起请求,一秒几百次都是很常见的,如何防止这类软件的重复无效请求,防止不断发起的请求也是需要我们针对性考虑的。
秒杀最终的本质是数据库的更新,但是有很多大量无效的请求,我们最终要做的就是如何把这些无效的请求过滤掉,防止渗透到数据库。限流的话,需要入手的方面很多:
前端限流:首先第一步就是通过前端限流,用户在秒杀按钮点击以后发起请求,那么在接下来的5秒是无法点击(通过设置按钮为disable)。这一小举措开发起来成本很小,但是很有效。
同一个用户xx秒内重复请求直接拒绝:具体多少秒需要根据实际业务和秒杀的人数而定,一般限定为10秒。具体的做法就是通过redis的键过期策略,首先对每个请求都从String value = redis.get(userId);如果获取到这个value为空或者为null,表示它是有效的请求,然后放行这个请求。如果不为空表示它是重复性请求,直接丢掉这个请求。如果有效,采用redis.setexpire(userId,value,10).value可以是任意值,一般放业务属性比较好,这个是设置以userId为key,10秒的过期时间(10秒后,key对应的值自动为null)