觉得不错请按下图操作,掘友们,哈哈哈!!!

【干货】常见库存设计方案-各种方案对比总有一个适合你

一:背景

某个票务体系比方12306占座,演出等, 流量最高的事务场景是在查询座位图和锁座环节,新的票务体系在优化后用了新的扣位占座体系,一起锁座扣位环节用新库存服务支撑,锁座&下单环节分别做预占、扣减库存操作,查询座位图由静态座位图加上实时座位图,静态座位图来自根底数据,实时座位图(预占+已占)来自新库存服务,票务库存与电商库存的区别在于电商库存只需控制加减防止超卖,而票务库存需求准确到座位,重视座位不重卖和少卖

【干货】常见库存设计方案-各种方案对比总有一个适合你

【干货】常见库存设计方案-各种方案对比总有一个适合你

二:功用

  • 查询库存:座位图查询库存
  • 预占库存:锁座时预占库存
  • 扣减库存:出票时扣减库存
  • 开释库存:超时未付出开释&退款时开释库存
  • 预留库存:保存座位,预留,或者后边再卖

三:事务流程

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 预占库存时机

常规电商都是在下单环节预占库存,付出成功后扣减库存,但票务在线选座有个前置环节是选座,所以预占库存能够前置到选座,而不是在下单环节,在付出成功后进行扣减库存操作。

  • 歹意预占库存 假如有用户在开始歹意预占许多库存但不下单,导致票导致后续有许多票没有卖出?预占并不是实践扣减,后台体系会自动开释预占超越15分钟的库存,从头放出来售卖,这种在现有电商体系现已很常见了,但用户仍是能够从头歹意预占,这种只能经过风控和反作弊行为来约束,详细方式有许多,用户约束,ip约束,手机PIN约束等。

  • 预占失利导致查询库存量变大

在抢手场次会呈现用户抢座,而抢座失利的用户会高频改写座位图信息从头选座,导致查询库存的恳求量瞬时添加3-5倍。

四: 那么终究体系规划要遵循什么准则???

  • 忍受动态座位图时刻短不共同,承受终究共同性,有必要确保高可用;

五 :详细规划

  • 恳求量最大的是查询库存,防止查库操作
  • 预占库存需求考虑多并发场景,防止重卖,能够用数据库唯一索引防止重卖
  • 守时开释超时的预占库存,防止少卖

5.1 计划一

本计划彻底根据同步操作

5.1.1预占库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 预占库存不能彻底靠mysql索引来防止重卖,需求用redis先做一层防重,这样能够最大确保数据库的恳求量等于实践座位数;
  • 在redis出问题时导致流量全部击穿到mysql,此刻需求在mysql操作上加入流控熔断,宁可部分预占失利,也要确保服务可用;
  • 先刺进mysql后写入redis,防止写redis成功但刺进mysql失利导致少卖;

5.1.2扣减库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 付出成功后扣减库存,修正库存状况为已占用;

5.1.3 查询库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 查询库存时只查询redis里边的数据,不允许穿透到数据库呈现数据;

5.1.4 超时开释库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 守时使命扫描状况为预占状况且超越15分钟的库存记录;
  • reids操作失利则开释库存失利;

5.1.5 问题

  • redis和mysql呈现不行用状况怎么办?
阶段 redis不行用 MySQL不行用
1 查询库存 部分影响,事务或许呈现重卖 无影响
2 预占库存 部分影响,事务或许呈现重卖 事务不行用
3 扣减库存 无影响,事务正常出票 事务不行用
4 开释库存 部分影响,或许呈现少卖 部分影响,或许呈现少卖

mysql不行用的状况是不能忍受的,会彻底堵塞事务流程,所以数据库的击穿都需求有流控熔断防范措施;

redis不行用的状况确认或许呈现2种事务场景,redis动态座位图数据少了导致部分重卖失利, redis动态座位图多了导致少卖所以redis的数据准确性至关重要。

  • 重卖状况怎么确保redis数据的终究共同性?

只有1和2的事务场景下redis不行用时才会呈现重卖,重卖底层有数据库唯一索引做确保,一旦呈现重卖数据库会抛出索引重复反常(DuplicateKeyException),只需捕捉到反常再将库存补到redis就能够防止下次重卖,数据呈现一次重复后就可达到终究共同性,假如没有反常呈现但缓存数据一向不共同,也不影响事务,表明该场次没有用户选此座位。

  • 少卖状况下怎么确保redis数据的终究共同性

少卖会直接带来丢失,怎么确保不呈现少卖至关重要,只有4的事务场景才会呈现少卖,只需确保守时task能够重试就能够确保少卖的状况。

  • 怎么处理场次售罄?

当场次所有座位都预占或扣减,场次状况需求变成售罄,在售罄状况也能够变回售卖状况,此逻辑正常由场次服务担任,但库存能够在库存呈现变化时异步周知场次服务。

  • 关于查询库存的功用问题?

当时采用redis的set结构来做库存结构缓存,set的的SMEMBERS操作是一个O(N)的操作,在功用上还需求验证,秒杀最大的流量在于查动态座位图,每个场次的座位数在[200,1000]之间,SMEMBERS的功用问题会带来很大隐患,所以暂时抛弃使用SMEMBERS查询动态座位图的计划。

5.2 计划二(异步操作)

本计划是根据异步操作规划 根据计划一的SMEMBERS操作功用问题,考虑到异步操作缓存库存来优化查询功用。

5.2.1预占库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 相对于计划一,预占完成后引进MQ来异步记录已售座位图,防止多线程下修正缓存的同步问题;

5.2.2扣减库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 相似计划一

5.2.3 查询库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 直接redis缓存查出已售座位图数量;

  • 缓存动态座位图或许呈现少卖状况,所以这儿需求触发动态座位图守时更新机制,经过发送MQ异步更新。

5.2.4 撤销库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

5.2.5 超时开释库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 相对于计划一,引进MQ来异步开释预占位图;

5.2.6 异步更新动态库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • MQ经过taskId来区分不同的partition(不同使命分组);

5.2.7 异步比对缓存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 依赖查询库存触发守时比对动作,从redis拿到库存与数据库做比对,假如呈现少卖状况发送mq删去redis中的数据。

5.2.8 问题

  • redis和mysql、MQ呈现不行用状况怎么办?
阶段 redis不行用 MySQL不行用 MQ不行用
1 查询库存 部分影响,用户看到已售座位是未售状况,实践不呈现重卖 无影响 无影响
2 预占库存 部分锁座流量被拒,实践不呈现重卖 库存事务不行用 部分影响,用户看到已售座位是未售状况
3 扣减库存 无影响 库存事务不行用 无影响
4 超时开释库存 无影响 部分影响,或许呈现少卖,重试确保 部分影响,或许呈现少卖
5 撤销库存 部分影响,座位图一守时刻内不能售 部分影响,或许呈现少卖 部分影响,或许呈现少卖
  • 异步更新动态座位图,用户看到的实时座位图会有多久的推迟?

每次异步更新座位图需求做setNX→ get→set 3次缓存操作,假如每次按照50ms来核算,一个一般使命有200个座位,在秒杀状况下,终究一个用户看到完整实时座位图的耗时是 200*50ms=1s,单个场次秒杀终究一个用户抢座失利点击改写座位图只需超越1秒就能看到准实时座位图,所以能够经过交互来一些优化,防止用户由于座位图更新不及时多次锁座失利的场景。

  • 上述4和5的状况下会呈现少卖,怎么防止?

能够经过在查询座位图的逻辑,每隔N分钟去校验缓存数据和数据库数据是否共同,只校验少卖的数据,呈现缓存中有而数据库没有的座位(少卖),能够发送到MQ移除缓存数据,来开释座位,为什么在查询座位图逻辑重触发?由于没人查询座位图就不会呈现少卖的状况。

  • MQ怎么确保 add 和 delete 操作的次序性?

用户下单预占后,撤销订单,预占库存和开释库存距离较短,add和delete操作经过mafka异步同步到动态座位图缓存,无法确保操作次序性,会有两种状况:1、先add再delete,正确,无影响;2、先delete再add,错误,会导致少卖,因add操作后就无法开释。这种状况经过守时更新机制来做。

  • 满座怎么做?

在创建对应使命时写入库存总量,每次出票时去修正库存量,当库存为0时主动发送MQ告诉到办理体系,提供查询库存余量的接口。

5.3 计划三

本计划根据 异步+MQ 计划二是经过过程7异步比对来达到缓存和数据库终究共同,从而防止少卖,但整个流程过于杂乱,库存的各个过程之间耦合很严重,不利于体系维护,计划二中会呈现少卖状况都是由于预占库存没有开释,而已售库存不会导致少卖,所以是不是能够把缓存分为预占库存和已售库存,缓存的预占库存能够守时失效,从而确保数据守时改写达到终究共同性。

  • redis存入2个key,value分别是已售座位和预占座位信息
  • 缓存预占座位信息设置N分钟的过期时刻。

5.3.1预占库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 相对于计划二,刺进数据库前先从缓存获取已售座位,判断座位是否已售,削减数据库的压力;
  • 发送MQ异步修正缓存中的预占库存;

5.3.2扣减库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 同步修正缓存的已售库存,修正已售库存失利需求上游做轮训确保;

5.3.3 查询库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 查询缓存中的已售库存+预占库存;

5.3.4 撤销库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 发送MQ异步更新缓存中的预占库存

5.3.5 超时开释库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

5.3.6 MQ异步更新预占库存

【干货】常见库存设计方案-各种方案对比总有一个适合你

  • 同计划二,预占库存设置了N分钟超时时刻,每次更新做一次N分钟续签;

5.3.7 问题

  • 预占缓存失效了怎么办?

1、假如是抢手使命预占库存的频率会很高,而MQ异步更新预占缓存会做续签操作,能够防止预售场次缓存失效导致许多因动态座位图显现不准确锁座失利的状况;

2、在低峰区,假如用户A预占了场次,N分钟没操作,一起N分钟内也无其他用户预占库存导致开释了库存,此刻B预占相同座位会呈现预占失利的状况,这种状况改写座位后该座位就会变成预占状况,所以低峰期会呈现小概率的锁座失利。

  • 预占缓存N设置几分钟适宜?

1、正常预占座位有效时长是15分钟,比方12306的扣位时效是15分钟,假如N设置成15分钟最合理,但要考虑开始抢票前前15分钟会呈现少卖case或歹意预占的状况导致场次真实少卖,所以主张N设置越小越好,但设置太小就会导致虚假重卖(用户锁座失利)的状况,伤害用户体会,需求在2者之间权衡,能够根据详细场景摸索设置。

  • 怎么确保缓存已售座位和数据库终究共同?

1、在退票和扣减库存操作时,确保redis的操作是同步的,操作redis失利就回来扣减、退票失利,由上游体系做重试确保数据终究共同性。

  • redis和mysql呈现不行用状况怎么办?
接口/功用 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/撤销保存 部分影响,少卖状况 不行用期间:事务不行用恢复后,事务恢复正常,无少卖与重卖

本文正在参与「金石计划」