22.微型秒杀模型

1.1 关于微型

微型并不是说并发量比较小或者逻辑比较简单。微型的意思是指库存比较小。而库存就是指我们要抢的东西的总量。当库存比较大的时候,架构和逻辑复杂度也会有相应的变化。

1.2 场景分析

我们有100个宝贝,这些宝贝非常抢手,很多人都想得到。但很显然,最终真正得到宝贝的人只有100个。如果出现了超过100个人购买成功了就是超卖,最终超卖的部分只能通过协商解决了。但是从发起购买到购买成功的过程中会有一个支付的过程。必须支付成功才能购买成功。因此如果库存的增减不恰当可能会出现假卖的情况。也就是有可能很多人同一时间购买,但大家都没有付款成功。后面的人来买的话提示没有库存了,事实上这些宝贝还没有卖出去,最终导致想要买宝贝的人没能买到。整个购买模型如下图所示:

22.微型秒杀模型_第1张图片

这里的关键在于会异步调用第三方支付网站完成支付,在这个过程中,库存应该怎样减?在哪个步骤减?如何处理支付问题等很多问题会影响商品购买流程。

1.3 一种假设

如果我们的网站架构师单实例单库,很多问题会非常简单。我们可以通过加锁,限流,串行化等手段解决。但是这样的网站架构显然不满足高并发,分布式的要求,不能承受较大的点击量。因此,我们的秒杀模型都是基于多实例,分布式,Redis缓存,MySQL分表分库等架构。能够承受较大的并发。

二 几种秒杀模型分析

2.1 在支付前扣减库存

为了防止超卖,我们可以在跳转到第三方支付进行支付之前扣减库存,这样可以有效防止商品因为超卖而导致的退款流程。但是很显然,这样会带来假卖的问题。假如一下子非常多的用户涌进来,他们占完了全部库存,但是迟迟不付款或者支付失败。导致后面的用户点击购买的时候提示没有库存而无法购买。一种稍微好点的办法是在支付成功时统计一下购买成功的总数,用总库存减去购买成功数得到剩余库存做一次修正。这样可以修正某些场景,但是如果有恶意用户,提交很多不支付的订单将库存消耗完(占着茅坑不拉屎),就会导致假卖问题。

2.2 在支付成功后减库存

为了防止2.1模型的假卖问题,我们将扣减库存问题放在支付成功后。这样可以解决假卖问题,但是会带来超卖问题。设想这样一种场景:大量用户点击购买,已经跳转到第三方支付网站进行支付,因为我们还没有获得支付结果,支付网关不会回调我们的支付完成逻辑,因此不会扣减库存。这样可以让无限的用户进入支付环节。最终如果这些用户全部支付成功显然会导致大量超卖的情况。

2.3 排队模型

联系到现实生活的场景,这样的问题最好的办法就是引入排队机制。排在前面的人可以成功购买,后面的人如果没有库存自然无法购买。大家按照时间顺序先后来,如果中间有人放弃,自然有后面的人顶替。这样自然保证了公平,公正。而且也不会出现超卖假卖问题。

这个思路是对的,关键是如何实现的问题。

三 实现排队模型

排队模型里有一个关键问题,就是锁票超时机制。比如我们买电影票,飞机票。都有一个锁票支付机制。超时之后如果没有完成支付则自动释放当前锁定的票资源,重新进入票池给别人购买。这样可以解决占着茅坑不拉屎的问题。

3.1 单实例模型

假如是单实例模型,我们可以将发起的请求全部放入一个数组,这个数组的长度就是宝贝数量(微型的意义在于宝贝数量较少,可以保证这里不会占用太多的空间)。启动一个线程,专门处理那些超时或者中途放弃的用户,以便让新用户能及时进入排队购买。但是假如这个线程挂掉或阻塞了,同样会导致假卖的问题。

因此较好的办法就是设置自动超时的缓存,对于超时的用户自动清除,以便让后来的用户可以进入支付环节。这个缓存可以自己实现,也可以使用Guava。

3.2 多实例分布式模型

显然对于多实例分布式模型设置本地缓存是行不通的。多实例分布式的话可以借助Redis缓存。但是Redis缓存的超时都是以key为基础的。使用哪种缓存结构可以实现我们的需求?

想来想去只有结合两种模式。Hash和String。使用String存放单个的用户,代表排队位。谁先支付完成谁就买到票。使用Hash统计排队人数。每次加入的时候,先遍历Hash,将因为超时失效的String用户剔除掉,这样就自然释放了资源。

类似于这样:任何一个用户进来必须先获得一张入场券,这个入场券的有效时长是设定的超时时间。如果在这个时间内完成了支付,则购买成功。如果没有支付,则超时后下一名购买者进来的时候自动剔除,同时获得这张券。当一个用户购买成功后,释放这张券。每个用户进来的时候,都要保证当前发放的入场券总量不超过库存剩余总量。当发放的券总量等于库存剩余的时候,后面的人自然提示暂时没有空余票,稍后再试。

这样我们就避免了另外开启线程来清除超时释放库存,也有效地在支付前控制了假卖,在很大几率上避免了超卖。但是存在一个问题,因为每个用户进来我们都要循环遍历一遍缓存,清理超时用户。因此库存不宜过大,否则循环耗时太长,用户体验不太好。

四 总结和拓展

对于秒杀模型,可能出现的问题就是假卖和超卖问题。而解决这个问题的办法就是排队和超时机制。如果我们的库存非常大(这样就不需要秒杀了),在最后库存余量不是很大的情况下如果出现高并发没有排队和超时机制同样会出现超卖和假卖问题。为了保险起见,其实在运营手段上也应该采取一些办法,比如本来总共1000件商品,我只买980件,剩余20件用于补给超卖部分。考虑到用户体验和退款等,宁愿少卖也不多卖。当然这些应该根据具体的业务需求来决定。

假如是库存非常大的情况,我们也可以考虑化大为小。在分布式环境下,将其平均分配,每台实例上卖一部分就将库存消化了。也可以引入大型排队系统和超时回调机制。当超时时自动触发一些业务流程。当然这些实现需要我们自己写很多代码,不能依赖Redis等缓存超时机制了。

你可能感兴趣的:(22.微型秒杀模型)