原创整理,转载请注明出处
电商系统关于订单和库存的几个问题
最近参加面试,被问及电商项目中如何处理并发情况下用户下单、减库存、事务一致性等问题。由于之前的项目类似于一个单体项目,对于高并发、高可用的的设计未有考虑,故面试问答很不流畅,有一定的相关概念,却没有一整套解决相关问题的流程。所以决定做个总结,以备后用。
总结起来有一下几个问题:
- 系统是如何保证高并发下用户体验的?
- 扣减库存是下单扣减还是支付扣减?为什么?
- 对于库存的扣减,如何保证不超卖?
- 如何控制恶意库存占用?
问题一:系统是如何保证高并发下用户体验的?
首先我们来明确一个概念:并发。并发是什么?有人说并发就是两个或多个任务一起执行。在单核CPU中,执行任务的线程是交替获取cup时间切片的,由于切换并执行非常快,在外界看来,多个任务是一起执行的。实际上,它们是交替执行的。在虚拟机中,有个专门的内存区域叫程序计数器,它的目的就是记录每个线程执行的字节码的行号指示器,以应对线程切换执行时,切入执行时机的问题。与并发类似的概念是并行,它是真实地同步执行,所以只有在多核CPU中才会有并行地概念。
所以,对于高并发用户体验可用性问题,实际上就是系统地性能能达到什么样地程度问题。
这当中,数据库的访问性能往往又是系统的性能瓶颈。根据经验数据,用户在访问互联网时,超过90%的操作只是读取数据,提交、修改的数据不到10%。因此可以将内容相对固定、主要供用户浏览的页面生产缓存,而无需访问数据库。
对于静态内容(网页、图片、音频文件、脚本文件等)可以选择CDN(Content Delivery Network,内容分发网络)方式发布,从而通过专业内容发布服务提高网站访问速度。频繁修改的数据可以采用缓存的办法处理。
问题二:扣减库存是下单扣减还是支付扣减?为什么?
核心思路是预锁库存,即下单时扣减,支付设置一个有效期,超过有效期释放库存。对于可能的恶意库存占用,限定用户在某个时间内的购买数量(或者达到一定数量提示其转到大客户购买处,单独处理)(PS:有位面试官牢牢抓住恶意库存占用问题问,最后聊得很尴尬,其实这本质上是产品得取舍问题,哪怕是支付后扣减,也有诸如支付了却提示库存不足得问题,这就要看产品根据实际情况做出权衡,技术上总归有实现思路的)。
摘抄他人总结的12306的实现思路
因为买火车票和购物不一样,购物可以付款后出库,但是买票这种,支付前就必须出库,因此,要将出库过程提前, 只有出库成功,才能生成订单,同样要引入redis库存
先扣缓存中的库存,扣除成功后,然后才可以去扣mysql中的库存
如果扣除缓存中的库存失败,就会挡在外面,返回库存不足,这些请求不会穿刺到mysql中,挡住了大多数的请求压力。
redis库存会和mysql库存不一致,极端情况下是肯定有的,需要进行库存同步
3.1 当缓存库存比数据库库存多,那么就会出现,查询有票,但是就无法下单,下单的时候就说库存不足, 这样也不会超卖,当redis的库存多的那部分扣完了,就可以把请求全部当在外面了。 对于12306,有时候,查询的时候有票,但是下单的时候返回库存不足,然后重新查询发现还是有库存, 这种情况应该就是redis中库存和mysql中库存不一致造成的。
3.2 当缓存库存比数据库缓存少,那么不会出问题,只会出现有票,但是没有出售的情况,等完成库存同步一下, 明天又准确了。
3.3 当然,mysql扣除库存的部分,还需要在前面加入队列缓冲,避免请求过多,让应用程序或数据库崩溃。
问题三:对于库存的扣减,如何保证不超卖?
方式一
可以对读操作加上显式锁(即在select …语句最后加上for update)这样一来用户1在进行读操作时用户2就需要排队等待了
但是问题来了,如果该商品很热门并发量很高那么效率就会大大的下降,怎么解决?
我们可以有条件有选择的在读操作上加锁,比如可以对库存做一个判断,当库存小于一个量时开始加锁,让购买者排队,这样一来就解决了超卖现象。
方式二
数据库表增加版本字段如version,每次修改时版本号+1
如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。
PDO update更新后,不但要验证返回状态是否为true,并且同时验证影响行数是否大于0
方式三 redis的队列来实现
将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。这个操作起来很方便,而且效率极高,最终我们采取这种方式来实现
问题四:如何控制恶意库存占用?
首先,这个问题是否真实存在,或者说发生概率有多大?
产品设计有时候是一个博弈的过程,如果一个功能需要付出10的成本,却只覆盖了1的需求,这个功能是否真的需要投入开发?举个栗子,打车平台的车主入驻需要验证车主真实身份,在真实性安全性方面做好保障,某打车平台A在车主入驻的时候需要填写繁复的资料,比如验证身份证,举着身份证拍照等,结果导致只有5%的车主愿意走完整个验证流程,但实际上虚假的车主可能仅占1%,为了这1%的车主放弃了另外94%的服务者,是不是太亏了?
当然不是说问题小,就可以不去解决,但是可以考虑有没有侧面的低成本解决方案。
延续上面的例子,另外一个打车平台B在车主入驻的时候仅需要填写基础资料,那如何验证司机真实性呢?机智的B公司邀请司机绑定银行卡,载客收益到账是司机的最基本需求,而银行卡是与个人身份绑定的,已经经过银行验证过用户真实性的,较低的注册成本使得大部分的司机都愿意走完注册流程,开始使用B产品。
下面正面回答下问题,因为非电商行业,所以仅靠逻辑推理,如有错误,请及时沟通
1、反作弊策略。定义恶意下单的时间频次,IP或者UID信息,当操作行为触发反作弊策略后,可以对该商品的库存锁定机制做调整,对于某些运营活动的爆款需要设立白名单或者调整反作弊策略;
2、允许超卖。库存100件,允许超卖x%;
3、调整库存锁定/解锁策略,比如在下单后5min才触发库存锁定,或者下单后15min未支付则解除库存锁定;
5、当库存小于一定量时,界面设计上则显示库存紧张,以用户支付请求为节点,“先支付先得”。
参考:
电商产品如何防止恶意下单导致的库存被占用?https://www.pmcaff.com/discuss/index/1000000000162678?from=related&pmc_param%5Bentry_id%5D=1000000000163080
高并发电商平台设计 http://www.legendshop.cn/new_547.html
用Redis轻松实现秒杀系统 https://blog.csdn.net/lida1234567/article/details/82866617
如何解决电商网站超卖现象 https://blog.csdn.net/u013521220/article/details/78839307
关于电商库存扣除实现思路 http://www.fecmall.com/topic/648