如何保障服务器承受亿级流量

限流

前面介绍过秒杀系统的架构方案,其中涉及了限流的相关内容,因篇幅有限,当时并没有将这部分内容展开说明,本篇就着重讲解限流的相关知识。

为了便于理解,还是先从业务场景入手。

业务场景:如何保障服务器承受亿级流量

在某次秒杀活动中,总计有100个特价商品,且每个商品的价格都非常低,活动计划于当年10月10日晚上10点10分0秒开启。

当时,服务架构如图11-1所示,所有客户端的API请求先进入Nginx层,再由Nginx层转发至网关层(Java,使用Spring Cloud Zuul),最后转发至后台服务(Java)。

• 图11-1 服务架构图

公司预测到秒杀开始那一瞬间会有海量用户涌入,致使系统无法处理所有用户请求。为保障服务器承受住大流量,只能通过限流的方式将部分流量放入后台服务中。

那什么是限流呢?一说到限流,有些人会把它与熔断混在一起讨论,其实它们是有区别的。

熔断一般发生在服务调用方,比如服务A需要调用服务B,调用几次后发现服务B出现了问题且无法再调用,此时服务A必须立即触发熔断,在一段时间内不再调用服务B。

限流一般发生在服务被调用方,且主要在网关层做限流操作。比如一个电商网站的后台服务一秒内只能处理10万个请求,这时突然涌入了100万个请求,该怎么办?此时,可以把90%的请求全部抛弃且不做处理,然后重点处理其余10%的请求,以此保证至少10万人能正常操作(这个比例看起来有点夸张,但是在实际秒杀场景中,即使把99%的流量抛弃掉也不要紧)。

再回到这一章的业务场景中,这次项目的需求是在某个层级通过限流的方式将秒杀活动的交易TPS控制在100笔/秒(因为秒杀活动总计100个商品,也就是说最终的交易只有100笔,希望100笔交易在一秒内完成)。此时应该怎么做呢?这就需要用到限流的一些常用算法了。

限流算法

关于限流的算法分为固定时间窗口计数、滑动时间窗口计数、漏桶、令牌桶4种,下面分别进行说明。

固定时间窗口计数算法

假设需求是后台服务每5秒钟处理500个请求(以5秒为单位方便举例),那么每5秒钟就需要一个时间窗口来统计请求,见表11-1。

表11-1 时间窗口各时间段统计

 

此时固定时间窗口算法看起来是可以满足需求的,不过它会存在一个问题。

假设1~4秒有200个请求,5秒时有300个请求,6~9秒有499个请求,10秒时有1个请求,通过计算得知:1~5秒总计500个请求,6~10秒也是总计500个请求。

通过以上统计,流量并没有超出阈值。但是如果计算一下5~9秒这个区间的请求数,会发现它已经达到了300+499=799个,也就是说5~9秒的请求数超标了299个,服务器明显支撑不住。

因此,固定时间窗口计数算法在现实中并不实用。

滑动时间窗口计数算法

假设项目需求是后台服务在一秒内处理100个请求,滑动时间窗口计数算法就是每100毫秒设置一个时间区间,每个时间区间统计该区间内的请求数量,然后每10个时间区间合并计算请求总数,请求数超出最大数量时就把多余的请求数据抛弃,当时间节点进入下一个区间(比如第11个区间)时,便不再统计第1个区间的请求数量,而是将第2~11个区间的请求数量进行合并来计算出一个总数,并以此类推,如图11-2所示。

• 图11-2 滑动时间区间示意图

虽然滑动时间窗口计数算法并不能保证每秒的统计请求数都是精准的,但是可以大大减少单位时间内请求数超出阈值且检测不出来的概率。比如,请求都堆积在前100毫秒的尾端与后100毫秒的首端时,才可能出现请求数超出最大数量且不被发现的情况。

当然,可以将这个区间分得更细,比如设置10毫秒为一个区间。区间分得越细,计算数据就越精准,但是资源损耗也越多。

这个算法目前看来似乎已经能满足需求了。不过,场景是这样的:库存中只有100个商品,如果想把TPS控制在100笔/秒,将滑动时间窗口设置为1秒即可,即分成10个区间,每个区间100毫秒,此时就会出现在第一个100毫秒请求已经超出100个的情况,也就是说商品已经被秒光。

这时就有个问题,什么人能在100毫秒内完成点击购买、下单、提交订单的整个流程?可能只有机器人可以做到,也就是说秒杀商品基本是给机器人准备的,这并不是公司想要的结果。

再看看其他算法。

漏桶算法

漏桶算法的实现思路如图11-3所示。

• 图11-3 漏桶实现思路示意图

从图11-3中可以看到,漏桶算法的实现分为3个步骤。

1)任意请求进来后直接进入漏桶排队。

2)以特定的速度处理漏桶队列里面的请求。

3)超出漏桶负载范围的新请求直接抛弃掉,无法进入排队队列。

结合上一小节在一秒内控制100个请求的例子,可以把输出速度设置为100个/秒(即每10毫秒处理一次请求),再把桶的大小设置为100。因为漏桶算法是按先进先出的原则处理请求的,所以会出现最终被处理的请求还是前面那100个请求的情况,这就与滑动时间窗口计数算法遇到的问题一样了,最终商品都会被机器人买走。那如果把桶的大小设置为1,不就可以达到目的了吗?

再说一下令牌桶算法。

令牌桶算法

令牌桶算法的实现思路如图11-4所示。

1)按照特定的速度产生令牌(Token)并存放在令牌桶中,如果令牌桶满了,新的令牌将不再产生。

2)新进来的请求如果需要处理,则需要消耗桶中的一个令牌。

3)如果桶中有令牌,直接消耗一个。

4)如果桶中没有令牌,进入一个队列中等待新的令牌。

5)如果等待令牌的队列满了,新请求就会直接被抛弃。

• 图11-4 令牌桶示意图

再结合上面在一秒内控制100个请求的例子,如果使用令牌桶算法,则需要先把令牌的产生速度设置为100个/秒,等待令牌的排队队列设为0,这样就能满足秒杀限流的需求了。

那令牌桶数量到底设置为多少呢?如果设置为100,假设令牌在秒杀前已经产生,那么秒杀开始时请求数已经是100了,前100个请求就会被放行,也就是说机器人又抢到了所有商品。

此时可以设置令牌桶数量为10,这样可以保证最多只有10个机器人抢到商品。

posted @ 2022-12-08 21:57  智慧园区-老朱  阅读(50)  评论(0编辑  收藏  举报