Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
库存管理设计方案_如何简单有效管理库存,希望能够帮助你!!!。
某个票务系统比如12306占座,演出等, 流量最高的业务场景是在查询座位图和锁座环节,新的票务系统在优化后用了新的扣位占座系统,同时锁座扣位环节用新库存服务支撑,锁座&下单环节分别做预占、扣减库存操作,查询座位图由静态座位图加上实时座位图,静态座位图来自基础数据,实时座位图(预占+已占)来自新库存服务,票务库存与电商库存的区别在于电商库存只要控制加减避免超卖,而票务库存需要精确到座位,关注座位不重卖和少卖。
预占库存时机
常规电商都是在下单环节预占库存,支付成功后扣减库存,但票务在线选座有个前置环节是选座,所以预占库存可以前置到选座,而不是在下单环节,在支付成功后进行扣减库存操作。
恶意预占库存 如果有用户在开始恶意预占大量库存但不下单,导致票导致后续有大量票没有卖出?预占并不是实际扣减,后台系统会自动释放预占超过15分钟的库存,重新放出来售卖,这种在现有电商系统已经很常见了,但用户还是可以重新恶意预占,这种只能通过风控和反作弊行为来限制,具体方式有很多,用户限制,ip限制,手机PIN限制等。
预占失败导致查询库存量变大
在热门场次会出现用户抢座,而抢座失败的用户会高频刷新座位图信息重新选座,导致查询库存的请求量瞬时增加3-5倍。
本方案完全基于同步操作
阶段 |
redis不可用 |
MySQL不可用 |
|
1 |
查询库存 |
部分影响,业务可能出现重卖 |
无影响 |
2 |
预占库存 |
部分影响,业务可能出现重卖 |
业务不可用 |
3 |
扣减库存 |
无影响,业务正常出票 |
业务不可用 |
4 |
释放库存 |
部分影响,可能出现少卖 |
部分影响,可能出现少卖 |
mysql不可用的情况是不能容忍的,会完全阻塞业务流程,所以数据库的击穿都需要有流控熔断防范措施;
redis不可用的情况确定可能出现2种业务场景,redis动态座位图数据少了导致部分重卖失败, redis动态座位图多了导致少卖,所以redis的数据准确性至关重要。
只有1和2的业务场景下redis不可用时才会出现重卖,重卖底层有数据库唯一索引做保障,一旦出现重卖数据库会抛出索引重复异常(DuplicateKeyException),只要捕捉到异常再将库存补到redis就可以避免下次重卖,数据出现一次重复后就可达到最终一致性,如果没有异常出现但缓存数据一直不一致,也不影响业务,表示该场次没有用户选此座位。
少卖会直接带来损失,如何保障不出现少卖至关重要,只有4的业务场景才会出现少卖,只要保障定时task能够重试就可以保障少卖的情况。
当场次所有座位都预占或扣减,场次状态需要变成售罄,在售罄状态也可以变回售卖状态,此逻辑正常由场次服务负责,但库存可以在库存出现变化时异步周知场次服务。
当前采用redis的set结构来做库存结构缓存,set的的SMEMBERS操作是一个O(N)的操作,在性能上还需要验证,秒杀最大的流量在于查动态座位图,每个场次的座位数在[200,1000]之间,SMEMBERS的性能问题会带来很大隐患,所以暂时废弃使用SMEMBERS查询动态座位图的方案。
本方案是基于异步操作设计 基于方案一的SMEMBERS操作性能问题,考虑到异步操作缓存库存来优化查询性能。
阶段 |
redis不可用 |
MySQL不可用 |
MQ不可用 |
|
1 |
查询库存 |
部分影响,用户看到已售座位是未售状态,实际不出现重卖 |
无影响 |
无影响 |
2 |
预占库存 |
部分锁座流量被拒,实际不出现重卖 |
库存业务不可用 |
部分影响,用户看到已售座位是未售状态 |
3 |
扣减库存 |
无影响 |
库存业务不可用 |
无影响 |
4 |
超时释放库存 |
无影响 |
部分影响,可能出现少卖,重试保障 |
部分影响,可能出现少卖 |
5 |
取消库存 |
部分影响,座位图一定时间内不能售 |
部分影响,可能出现少卖 |
部分影响,可能出现少卖 |
每次异步更新座位图需要做setNX → get →set 3次缓存操作,如果每次按照50ms来计算,一个普通任务有200个座位,在秒杀情况下,最后一个用户看到完整实时座位图的耗时是 200*50ms=1s,单个场次秒杀最后一个用户抢座失败点击刷新座位图只要超过1秒就能看到准实时座位图,所以可以通过交互来一些优化,避免用户因为座位图更新不及时多次锁座失败的场景。
可以通过在查询座位图的逻辑,每隔N分钟去校验缓存数据和数据库数据是否一致,只校验少卖的数据,出现缓存中有而数据库没有的座位(少卖),可以发送到MQ移除缓存数据,来释放座位,为什么在查询座位图逻辑重触发?因为没人查询座位图就不会出现少卖的情况。
用户下单预占后,取消订单,预占库存和释放库存间隔较短,add和delete操作通过mafka异步同步到动态座位图缓存,无法保证操作顺序性,会有两种情况:1、先add再delete,正确,无影响;2、先delete再add,错误,会导致少卖,因add操作后就无法释放。这种情况通过定时更新机制来做。
在创建对应任务时写入库存总量,每次出票时去修改库存量,当库存为0时主动发送MQ通知到管理系统,提供查询库存余量的接口。
本方案基于 异步+MQ 方案二是通过步骤7异步比对来达到缓存和数据库最终一致,从而防止少卖,但整个流程过于复杂,库存的各个步骤之间耦合很严重,不利于系统维护,方案二中会出现少卖情况都是因为预占库存没有释放,而已售库存不会导致少卖,所以是不是可以把缓存分为预占库存和已售库存,缓存的预占库存可以定时失效,从而保证数据定时刷新达到最终一致性。
1、如果是热门任务预占库存的频率会很高,而MQ异步更新预占缓存会做续签操作,可以避免预售场次缓存失效导致大量因动态座位图显示不准确锁座失败的情况;
2、在低峰区,如果用户A预占了场次,N分钟没操作,同时N分钟内也无其他用户预占库存导致释放了库存,此时B预占相同座位会出现预占失败的情况,这种情况刷新座位后该座位就会变成预占状态,所以低峰期会出现小概率的锁座失败。
1、正常预占座位有效时长是15分钟,比如12306的扣位时效是15分钟,如果N设置成15分钟最合理,但要考虑开始抢票前前15分钟会出现少卖case或恶意预占的情况导致场次真实少卖,所以建议N设置越小越好,但设置太小就会导致虚假重卖(用户锁座失败)的情况,伤害用户体验,需要在2者之间权衡,可以根据具体场景摸索设置。
1、在退票和扣减库存操作时,保证redis的操作是同步的,操作redis失败就返回扣减、退票失败,由上游系统做重试保证数据最终一致性。
接口/功能 |
redis不可用 |
MySQL不可用 |
|
接口/功能 |
redis不可用 |
MySQL不可用 |
|
1 |
queryStockByTask/查询动态座位图 |
接口不可用,c端展示静态座位图; |
无影响 |
queryStockDetail/下单前查询库存座位信息 |
直接查询db(考虑主从问题) |
缓存可用情况下无影响;缓存不可用时,业务不可用 |
|
2 |
lockStock/预占库存 |
重卖问题由db索引保证;(注意db限流) |
业务不可用 |
3 |
unlockStock/解锁预占库存 |
||
4 |
submitStock/扣减库存 |
现状:获取分布式锁失败;预期:由db保证,业务无影响 |
业务不可用 |
5 |
releaseStock/释放库存 |
不可用期间:由db保证购票流程正常短暂不可用:部分影响,缓存未及时更新出现少卖情况 |
接口不可用,恢复后会有少卖情况 |
6 |
keepStock/保留库存 |
无影响 |
业务不可用 |
7 |
cancelKeepStock/取消保留 |
部分影响,少卖情况 |
不可用期间:业务不可用恢复后,业务恢复正常,无少卖与重卖 |
作者:小蚂蚁技术
链接:https://juejin.cn/post/
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章