高并发系统中库存热点的解决方案

导语

本文讨论的场景是互联网业务中秒杀场景下热key的解决方案,秒杀场景不局限于电商活动,也包括:日常活动下发奖品的配额控制、春节的集卡瓜分现金、春晚的摇一摇抢红包等。更进一步的说,是探讨所有关于有热key的场景的一种解决方案。

以上图为例,解释下各个模块: 发货模块:先查询库存,有库存后给用户发货,进入到账流程。

到账模块:把钱发到用户账户上。

使用模块:用户看到账户上的余额,进行提现/使用操作。

发货流程中,有高并发流量,有热点key。本文重点讨论这个流程。

到账流程中,有高并发流量,无热点key。一般只有库存数量的请求才会进入到这一步,用户的请求已经少了很多,而且这里可以按用户分区治理解决高并发问题(分set分库分表等)。本文不讨论此流程。

提现流程中,用户到账后,有提现需求的用户才会发起请求,本文不讨论此流程。

解决方案

按照业务规模和流量预估,将场景分成如下几类,分别探讨下热点的解决方案。

qps < 1k

高并发系统中库存热点的解决方案_第1张图片
直接利用DB存储库存、用户的结果数据。

  • <1kqps流量的服务,db层没有压力,直接利用DB做库存控制即可。
  • 可以利用从库分担读压力:

有库存时,已经抢到的人直接读取抢的结果; 没有库存时,没抢到的人直接根据读取的库存的结果,返回已经抢完。 还要谈一下安全的问题,接入层的节点可以定时cache黑产名单,提前拦截掉请求。

1k< qps < 10w

高并发系统中库存热点的解决方案_第2张图片
这个qps下,库存的DB是瓶颈,假设采用redis,单key可以扛住10wqps的读写,用redis存储库存信息。 抢到的用户才会进入到账,这部分的并发量取决于库存数,因此这里开始把发货和到账流程分开。用户流程分几步:

  • 用户首先查询是否抢过,已经抢到的用户,可以直接返回
  • 扣库存成功后,保证到账请求成功,如果失败的话,可以异步补单重试保证最终一致。
  • 到账模块把用户结果存储后,需要构建出用户结果的cache,用来将已经抢过的用户信息存储下来,把再次请求的量挡在前面。
  • 如果库存没有了,直接返回,告诉用户已经抢完。

如果扣件库存失败了,怎么处理?

  • 如果是超时错误,此时不能确定库存是否扣成功,不能发起重试,直接告诉用户失败,让用户发起重试。这里会带来真实发出去的货比预估的库存少,可以通过定时对账算出补偿值修正库存值,补偿值=库存预估数- 真实到账数-库存余额。
  • 如果是非超时错误,直接返回失败,让用户发起重试。

10w < qps < 100w

这个qps下,redis的单key也存在瓶颈了,可以采用分治的方法解决热key问题。 一般有两种方式:

  • redis中分10个key,库存数平均分布在每个key上,用户按照hash规则到固定的key上。优点是配额能够精准控制,缺点是: redis压力大,同时分key上的库存有碎片。被路由到在无库存的key上用户,哪怕先进来抢商品,也会提示没货。
  • 库存在存储中是单key,发货服务的每个机器节点定时从库存中取固定数量的库存,预扣库存方式放在本地cache做。优点是db压力小,缺点也很大:微服务节点如果被调度走的话,预扣的库存没有发货导致库存不准。同一个用户的体验单调性差,库存节点的预扣库存耗尽时,此时用户体验时无库存,节点再次拿到预扣库存后,会提示用户有库存。

这里提出基于瞬间高并发下热key的多分片本地cache方案,如下图所示。
高并发系统中库存热点的解决方案_第3张图片
库存存储按key分片,分片公式参考:max(min(库存数, qps/10w), 1)。新加进来的库存节点,从库存db中将数据loading到本地cache住。发货访问库存步骤如下:

  • 发货请求到库存节点,首先通过hash选取一个本地分片,本地分片查询到有库存后,去db扣减库存。
  • 如果本地cache和db数据不一致导致库存扣减失败,此时刚好更新本地cache值。如图所示,本地节点从1->0。
  • 重新挑选本地非0的分片。
  • 重复2的过程,此时发现有库存,但db中的库存数更少,需要更新本地cache9->6。

如果本地分片cache都是0,代表此时库存已经没有了,直接返回上游已经瓜分完毕。 本地的分片cache本质上是个bloom滤波器,告诉你有库存时不一定有,但是告诉你没有库存时一定没有。刚好应对高并发流量下,大量抢不到商品的用户应该快速返回告知用户结果。

这个方案也解决了用户时有时无问题,也解决了先来的用户瓜分不到,后面来的用户可以抢到的问题。对db的方案压力也不大。

需要特别说明的是,当db无库存时,当本地cache有库存时,会触发扣减库存,如果10个本地cache都不为0,会触发10次的扣减库存IO,但是很快会收敛到本地cache和db一致的情况,我们测试了20wqps下的请求的放大倍数在1.003756倍。

管理员补库存后,本地库存节点会定时取到库存,cache在本地节点。

整体架构如下:
高并发系统中库存热点的解决方案_第4张图片
这里引入了瓜分服务,分担发货服务的压力,针对没有资格的用户、已经瓜分过的用户,提前把流量拦截掉。

100w < qps < 1000w

这个量级的秒杀系统,我自己没有做过,跟大佬交流后参考大佬的做法:

  • 红包业务独立部署,不影响其他业务
  • 全部逻辑在线逻辑放到接入层,峰值流量下减少峰值的调用
  • 红包模块做两个事情:a)控制速率,匀速下发红包,保证后续的到账、提现的负载在可控范围;b)定时回收没发完的红包
  • 用户开始抢红包,真正抢到的才走到账逻辑。

高并发系统中库存热点的解决方案_第5张图片

结论

  • 能用db扛,优先db
  • 能用nosql单key扛,优先单key扛
  • 超过单key压力,分治
  • 流量大得离谱,分治+提前本地cache

你可能感兴趣的:(高并发,github,数据库,大数据)