在上一篇文章中我们讲解了微服务中的熔断处理方式,在实际开发中和熔断相配的技术就是限流,那么这篇文章我们将着重讲解限流的的知识。
一、案例
某电商平台要举行秒杀活动,服务架构设计如下:所有客户端的请求先进入Nginx层,再经过Nginx转发到网关层,最后转发给后台服务。预计秒杀开始的一瞬将将会有大量的客户端请求涌入,进而导致系统无法使用,因此为了保证系统可用,就引入了限流,允许部分流量进入后台服务。 在讲解具体限流算法之前我们先来看看限流和熔断的区别:
- 熔断大部分发生在服务的调用方:例如服务A调用服务B,调用多次后发现服务B无法使用,这时服务A就必须触发熔断,在一段时间内就不在调用服务B。
- 限流大部分发生在服务被调用发:服务A一秒只能处理1万次请求,这时涌入了3万个请求,那么为了保证服务可用就需要抛弃2万条请求,只处理1万条请求。
二、限流算法
限流算法分为固定时间窗口计数、滑动时间窗口计数、漏桶和令牌桶这四种。
2.1 固定时间窗口计数
比如后台服务每5秒中能处理50000个请求,那么每隔5秒就需要一个时间窗口来统计请求,如下表。
时间窗口 | 请求数 | 抛弃数 |
---|---|---|
00:00:01~00:00:05 | 50000 | 10000 |
00:00:06~00:00:10 | 50000 | 0 |
00:00:11~00:00:15 | 40000 | 0 |
00:00:16~00:00:20 | 65000 | 15000 |
这个固定时间算法看起来没有问题,起始它存在一个大问题: 比如在前四秒内有20000个请求,第五秒的时候有30000个请求,6~9秒的时候又有49900个请求,第十秒进来100个请求。我们计算得知5秒内一共有50000个请求,6到10秒内一共有50000个请求。流量并没有超过限定的阈值,但是我们发现第5秒到第九秒这个的请求数超过了阈值,这时服务端肯定撑不住了。因此固定时间窗口计数算法在实际开发中并不适用。
2.2 滑动时间窗口计数
比如后台服务1秒内可以处理100个请求,滑动时间窗口计数法将每100毫秒设置一个时间区间,每个时间区间统计该区间内的请求总数,接着每10个区间合并计算一次请求总数,如果请求总数大于阈值的话就抛弃多余的请求。当时间节点进入下一个区间时就不再统计第一个区间的请求数量。 滑动时间窗口计数虽然不能保证每秒统计的请求数量完全精确,但是很大的减少了单位时间内请求数超过阈值时无法检测到的情况。 当然这个方法也存在一定的问题,当我们将时间区间划分的很细时(比如每10毫秒算是一个区间),资源消耗也会增加。另外,对于秒杀系统来说,如过有100个商品,将100毫秒设置为一个时间窗口,那么商品将会在100毫秒内被秒杀掉,什么样的用户能在100毫秒内完成秒杀操作(购买、下单、付款),那当然就是机器人。
2.3 漏桶
漏桶算法的思路如下:
- 所有请求进来后直接进入漏桶队列排队;
- 以固定的速度处理漏桶中的请求;
- 当漏桶队列满了,后续的请求直接抛弃。
例如后台服务1秒内只能处理100个请求,那么我们可以把队列的输出(处理)速度设置为每秒100个,漏桶队列的大小设置为100,因为漏桶算法时基于队列实现的,因此优先处理先进来的请求,所以最终处理的还是前100条请求,因此这个算法和滑动时间窗口计数算法的结果时一样的。
2.4 令牌桶
令牌桶算法实现思路如下:
- 按照特定的速度产生令牌,并存放在令牌桶中,如果令牌桶满了,就不再生成新的令牌;
- 请求从桶中获取一个令牌;
- 如果桶中没有了令牌,那么请求就要等待;
- 如果等待令牌的队列满了,那么就直接抛弃后续的请求。
同样,如果后台服务1秒只能处理100个请求,那么我们可以将令牌的产生速度设置为每秒100个,等待令牌的队列大小设置为0,那么这就满足了秒杀限流的需求。 这里有个问题,令牌桶大小设置多少合适。如果设置为100,当秒杀开始时已经有100个请求过来了,那么商品还是会被机器人秒杀掉。这时我们可以将令牌桶的大小设置为10,就可以保证正常用户秒杀到商品了,最对也就10个机器人秒杀到商品。
三、考虑的问题
了解了限流的算法,这小节我们来看看要考虑的问题。
3.1 使用漏桶还是令牌桶
大部分情况我们希望限流是可以通用的,如果使用漏桶的话存在一个问题,在服务器空闲的时候本该一次性处理所有请求,但是漏桶请求处理速度是恒定了,因此无法快速的处理请求,因此这里推荐使用令牌桶。
3.2 在哪里实现限流
我们可以在两个地方进行限流,一个是Nignx层,一个是网关层。虽然Nginx中提供限流的插件,但是该插件是基于漏桶算法的,根据上一小节的分析,这个方案抛弃,因此推荐在网关层限流。
TIP:如果你想动态调整限流配置的话,可以使用Nginx Lua。
3.3 使用分布式限流还是统一限流
网关层具备负载均衡,因此我们可以共享一个令牌桶,也可以每个节点都是用自己的令牌桶。 使用分布式限流的话,就需要计算服务器数量,然后将TPS平均分布到各个服务器上。但是如果某些节点实现了,那么将会导致两个问题:
- 部分网关负载增加;
- 处理请求时间拉长。 使用统一限流的话,我们可以把令牌桶放在类似于Redis这样的Nosql数据库中。但对于秒杀这种短时间大QPS的情况,nosql数据库很可能会崩溃,一旦崩溃后台服务就会被拖垮。 具体使用哪个技术要根据项目实际情况确定,对于秒杀来说建议使用分布式限流。
四、注意事项
4.1 限流后返回给客户端什么
服务端可以设定特定的HttpCode来标识限流,以供客户端处理。客户端收到限流标识后,转换成特定的提示次显示即可。
4.2 实时监控和实时配置
对于限流要做好日志记录,以便再出现意外的时候查询。对于实时配置只需要做到在配置中心实现对令牌桶同台管理和实时设置即可,这样不仅方便秒杀场景,也方面其他场景的配置。
五、总结
本问主要讲解了限流的几种算法、注意事项和要考虑的问题。