您的位置首页  油气能源  非常规气

限流的非常规用途 - 缓解抢购压力

限流的非常规用途 - 缓解抢购压力

  这两年因为疫情,节假日都不怎么外出了,以前每逢节假日都要提前一个月或者半个月抢火车票,人太多的时候会把12306整崩溃。当时很多技术人员指点江山,激扬想法,粪土当年铁科院。

  前几年小米手机还很火爆,特别抢手,每每新机发布,人山人海、万人空巷,却往往都在米兔排队的身影后收获失望的表情。

  抢购,大部分人应该都经历过,没抢过火车票,还没抢过口罩吗?没抢过食盐,还没抢过米面粮油吗?没抢过618,难道没抢过双11?总有一款抢购适合你。

  “购”大家都能够理解,就是花钱买东西。“抢”就很有意思了,首先是因为商品数量有限(这里不讲套路了),然后需要的人特别多,所以怎么分配就成了一个问题,也就是人们常说的僧多粥少。

  不知道大家是否见过这样的场景,一群人围在商店门口,大门还没完全打开,就咆哮着挤了进去,抢着拿货架上的商品,谁跑得快挤得凶,谁就能拿到,谁拿到谁就可以去付款带走,这就是抢得最真实写照了。如果你不曾体验过,那么挤公交也可以代替,要么挤上去,刷卡走人,要么被挤下来,等待下一辆车。虽然体验不好,但也确实解决了分配问题。

  这里的所谓“抢”就是看谁先获得了有利位置,谁先锁定了商品。那么锁定了商品就一定会购买吗?也不见得,说不定你突然发现囊中羞涩,大家应该听过很多弃购的新闻。所以对于抢购来说,抢的是“购”的机会,谁先到了谁就有 “购” 的机会。至于会不会真的买回家,还真不好下定论。

  根据前面的理解,我们可以将抢购分为两个阶段,“抢”映射为下单,“购”映射为支付订单。下单的操作就是锁定商品,又可以分为锁定库存、创建订单两个阶段,锁定库存就相当于你在商店中把商品拿到了手里,创建订单就相当于你和商家关于商品的价格达成协议。

  操作库存有一个很有名的“超售”问题,说的是扣减库存时出错了,库存数量扣成了负数,那么这个问题是如何发生的呢?

  后端服务处理请求时一般都是多线程的,为了高可用,还可能是分布式的,这个问题就是多线程和分布式环境带来的并行处理造成的。扣减库存时,一般有两个步骤,先检查库存是否足够,然后再从库存中减去相应的数量。这两个步骤如果不是原子的,那么在多线程或分布式环境下,就会存在多个线程同时查询库存,单从每个请求处理的上下文看库存是足够,但是库存总量不能满足所有这些扣减加在一起,结果就扣成了负数。

  那怎么解决呢?方法很多,比如把查询和更新放到同一个数据库事务中就可以了,或者使用一个锁(分布式部署时为分布式锁)来锁定对某件商品库存的查询+扣减同时只能有一个在处理。

  这样是不是就没有问题了呢?还是有的。需要注意这里说的是抢购,意味着会有很多的人来尝试,在现实生活中,如果人特别多,商店中承载不了这么多人,很可能会出现安全事故。在计算机程序中也可能会出现耗尽资源导致崩溃的问题,特别是数据库,IO请求突然爆发,很可能就会被压垮,上文提到的数据库事务无疑会加重这一负担。而锁也会导致吞吐量的下降,请求堆积无法得到及时处理,一样会加重服务器负担,出现频繁超时甚至无法服务的问题。

  还有什么办法呢?以前写程序的时候有一个观点,大概是不用太关注代码运行效率,增加硬件就好了,比如增加内存、升级CPU、换固态硬盘等等。不过在抢购这里不太可行,还是因为并发量可能太大了,不得不考虑计算资源的成本,而动态资源调配的速度也是个问题。

  现实生活中我们还有一个文明的处理方式:排队,先到先得,售完为止。程序先把请求接下来,保存到队列中,然后再按照先后顺序一个个处理,处理完一个回复一个,这就是队列的处理方式。这样做的优点是不用再去协调那些跨线程跨进程的资源访问冲突,计算资源需求也会大幅降低;缺点是用户要多等一会才能看到结果,体验略差,不过本来就是很紧俏的东西。

  在软件系统中库存就是一个数字,限流呢,也有一个数字,我们可以把限流的阈值作为库存的数量,请求过来的时候,先用限流处理,能够通过的就进入下一步,如果被限流了,则说明库存已经耗尽了,返回错误就行了。示意图如下:

  这样做的好处是什么呢?减轻后续处理的压力。如果库存已经耗尽,也再无必要去查询数据库,白白浪费宝贵的数据库资源,甚至分布式锁、显式的数据库事务都不需要了,因为能够通过限流检查的就是可以扣减库存的,直接使用Update就可以了;不过此时扣减库存和下订单时的压力仍旧存在,比如3W个请求同时进来,限流可以拦截其中的27000,剩下的3000会进入下一环节,影响还是需要仔细考虑的。再看使用队列的方式,加了限流,队列中也只接收能够下单的请求,队列压力小,后续处理队列时的数据库操作同样也减少很多。

  这里可能还会出现一些问题,比如限流检查通过了,但是后续的处理因为某种原因失败了,库存不能回收,实际上就浪费了一次机会,不过这不是抢购的主要矛盾,一般会忽略这个问题。

  当然对于限购来说还有很多问题,限流就很难发挥作用了,比如前端高并发查询库存数,这里就不多讲了。

  这里以Core Web API为例,限流组件选择 FireflySoft.RateLimit ,算法选择简单的固定窗口也就是计数器方式,进程内计数。这里没有选择Redis,是因为进程内计数比较简单,不需要外部依赖,演示方便;即使在分布式环境下,一般也可以应对,比如总的限流阈值是 1000/小时,部署了5份服务实例,只需要在程序中使用 200/小时 的阈值就可以了;但是如果负载均衡不均匀,某些情况下可能不太合适,此时可以选择Redis全局一致限流。

  如果你不想在Web程序中使用,或者需要更多自定义的设置,也可以直接安装e 这个包,它可以用于各种.NET程序。点击查看示例:

免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186
  • 标签:非常规用途测验
  • 编辑:王虹
  • 相关文章
TAGS标签更多>>