1. 什么是限流
在高并发系统中,有很多手段来保护系统,比如:缓存、降级和限流等。
- 缓存的目的是提升系统的访问速度和增大系统的处理能力,可谓是抗高并发流量的银弹。
- 降级是当服务出问题或影响到核心流程的性能时,需要暂时屏蔽,待高峰流量过去或者解决后再进行打开的场景。 而有些场景不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(评论、下单)、频繁的复杂查询(评论的最后几页)等,因此,需要有一种手段来限制这些场景下并发/请求量,这种手段就是限流。
- 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,一旦达到限制速度则可以拒绝服务(定向到错误页或者资源没有了)、排队或等待(比如秒杀、评论或下单)、降级(返回兜底数据,如商品页库存默认有货)
在压测时我们能找出每个系统的处理峰值,然后通过设定峰值阈值,来防止当前系统过载时,通过处理过载请求来保障系统可用,
2. 为什么限流
明白了什么是限流,为什么要限流,那么互联网公司在各种业务大促中,为了保证系统不被流量压垮,会在系统流量到达设置的阈值时,拒绝后续的流量,限流会导致部分时间段(这个时间段是毫秒级的)系统不可用,不是完全不可用,一般衡量系统处理能力的指标是每秒的QPS或者TPS,假设系统每秒的阈值是1000,当这一秒内有1001个请求访问时,那最后一个请求就会被限流(拒绝处理)
3. 限流的常见几种算法
在具体开发中,尤其是RPC框架中,限流是RPC的标配,一般业务开发人员很少做限流算法开发,这也导致大部分开发人员不是很了解限流算法的原理,这里分享几种常用的限流算法,指出他们的优缺点,并通过代码实现他们。
3.1 计数器限流
你要是仔细看了上面的内容,就会发现上面举例的每秒阈值1000的那个例子就是一个计数器限流的思想,计数器限流的本质是一定时间内,访问量到达设置的限制后,在这个时间段没有过去之前,超过阈值的访问量拒绝处理,举个例,你告诉老板我一个小时只处理10件事,这是你的处理能力,但领导半个小内就断续断续给你分派了10件事,这时已经到达你的极限了,在后面的半个小时内,领导再派出的活你是拒绝处理的,直到下一个小时的时间段开始。
计数器限流的思考
加到1000了,那么剩余的900毫秒内就无法处理任何请求了,这种限流很容易造成热点,再来分析一种情况,在一秒内最后100毫秒时间内突发请求800个,这时进入下一个单位时间内,在这个单位时间的前100毫秒内,突发请求700个,这时你会发现200毫秒处理了请求1500个,好像限流不起作用了,是的,这是一个边界问题,是计数器限流的缺点。,如下图,黄线是第一个单位时间内,红线是第二个单位时间内。
3.2 令牌桶限流
令牌桶限流-顾名思义,手中握有令牌才能通过,系统只处理含有令牌的请求,如果一个请求获取不到令牌,系统拒绝处理,再通俗一点,医院每天接待病人是有限的,只有挂了号才能看病,挂不上号,对不起,医院不给你看病。 令牌桶,有一个固定大小的容器,每隔一定的时间往桶内放入固定数量的令牌,当请求到来时去容器内先获取令牌,拿到了,开始处理,拿不到拒绝处理(或者短暂的等待,再此获取还是获取不到就放弃)
令牌桶的思考
相对与计数器限流会比较复杂一些,令牌桶限流能够更方便的调整放入令牌的频率和每次获取令牌的个数,甚至可以用令牌桶思想来限制网关入口流量。
3.3 漏斗算法
漏斗限流,意思是说在一个漏斗容器中,当请求来临时就从漏斗顶部放入,漏斗底部会以一定的频率流出,当放入的速度大于流出的速度时,漏斗的空间会逐渐减少为0,这时请求会被拒绝,其实就是上面开始时池塘流水的例子。流入速率是随机的,流出速率是固定的,当漏斗满了之后,其实到了一个平滑的阶段,因为流出是固定的,所以你流入也是固定的,相当于请求是匀速通过的
4. 实践应用
4.1 RPC 限流到底怎么做的?
微服务盛行的时代,一个Application可能对付发布多个服务(A,B两个服务),一个服务可能存在多个方法(A1,A2,B1,B2),而且一个Application通常会部署多台机器,我们通常的限流可能会对某个服务限流,也可能对某个服务下面的方法限流,一般情况下RPC的控制台中支持限流的可视化,可配置化。
从上图来看,浏览器触发配置中心的限流规则变更,配置中心通知监听了该规则的服务器,这个时候可能是客户端限流,也可能是服务端限流,取决于浏览器上的操作,假设是服务端限流,那么每个服务端启动一个限流算法(可能是上面算法中的任意一个),这个时候是每台机器都在限流,相当于单机限流,各不影响。
5. 思考
5.1 问题一
我们介绍了三种限流算法,比如计数器限流,会开启一个协程定时检测重置计数变量为0,如果一个应用有很多个服务,是否意味着要开启很多个协程,那么有人说协程轻量级的,没事,但要是Java中的线程呢,怎么解决,思路是延迟重置,服务开始时,设置计数阈值,同时记录当前时间,每当请求来临时,我们只允许在当前时间段内并且计数变量没有到达阈值的请求通过,否则拒绝,当过了当前时间段,我们重置计数变量,这样是不是就不用开启新的协程了。
5.2 问题二
上面我们假设是服务端限流,那么到底该用服务端限流还是客户端限流,我们看这样一个示例,有一个A服务,部署了10台机器(相当于10个服务提供者),但调用A服务的有100个消费者(客户端),假设我们每台机器的阈值是1000,你怎么分给100个客户端呢?你也不了解他们的调用量,就比较麻烦,所以一般情况下都是在服务端限流,因为你自己的服务你最清楚。什么时候用客户端限流呢?当你明确的知道某一个客户端调用量非常大,影响了其它客户端的使用,这时你可以指定该客户端的限流规则
5.3 问题三
我们上面提到的都是单机限流,还是我们的A服务,部署了10台,但有一台机器是1核2G,其余是4核8G的,这时限流就麻烦了,不能用统一标准限流了,那么在分布式应用程序中,有没有分布式限流的方法呢?这里提供几种思路:
- Nginx 层限流,一般http服务需要经过网关,Nginx层相当于网关限流
- Redis限流,redis是线程安全的,redis支持LUA脚本
- 开源组件Hystrix、resilience4j、Sentinel
相关文章