一、服务限流的作用及实现
在没有任何保护机制的情况下,如果所有的流量都进入服务器,很可能造成服务器宕机导致整个系统不可用,从而造成巨大的损失。为了保证系统在这些场景中仍然能够稳定运行,就需要采取一定的系统保护策略,常见的策略有服务降级、限流和熔断等。
限流的主要目的是通过限制并发访问数或者限制一个时间窗口内允许处理的请求数量来保护系统,一旦达到限制数量则对当前请求进行处理采取对应的拒绝策略,比如跳转到错误页面拒绝请求、进入排队系统、降级等。从本质上来说,限流的主要作用是损失一部分用户的可用性,为大部分用户提供稳定可靠的服务。
在实际开发过程中,限流几乎无处不在
- 在Nginx层添加限流模块限制平均访问速度
- 通过设置数据库连接池、线程池的大小来限制总的并发数
- 通过Guava提供的Ratelimiter限制接口的访问速度
- TCP通信协议中的流量整形要实现限流,最重要的就是限流的算法,下面简单来讲解一下常见的限流实现算法
二 、常见的限流算法
2.1计数器算法
计数器算法是一种比较简单的限流实现算法,在指定周期内累加访问次数,当访问次数达到设定的闻值时触发限流策略,当进入下一个时间周期时进行访问次数的清零
如图7-2所示,限定了每一分钟能够处理的总的请求数为100,在第一个一分钟内,一共请求了60次。接着到第二个一分钟,counter又从0开始计数,在一分半钟时,已经达到了最大限流的闯值,这个时候后续的所有请求都会被拒绝。这种算法可以用在短信发送的频次限制上,比如限制同一个用户一分钟之内触发短信发送的次数
这种算法存在一个临界问题,如图7-3所示,在第一分钟的0:58和第二分钟的1:02这个时间段内,分别出现了100个请求,整体来看就会出现4秒内总的请求量达到200,超出了设置的闻值。
2.2 滑动窗口算法
为了解决计数器算法带来的临界问题,所以引入了滑动窗口算法。滑动窗口是一种流量控制技术,在TCP网络通信协议中,就采用了滑动窗口算法来解决网络拥塞的情况。
简单来说,滑动窗口算法的原理是在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的所有小时间窗口总的计数即可。
如图7-4所示,我们将一分钟拆分为4个小时间窗口,每个小时间窗口最多能够处理25个请求。并且通过虚线框表示滑动窗口的大小(当前窗口的大小是2,也就是在这个窗口内最多能够处理50个请求)。同时滑动窗口会随着时间往前移动,比如前面15s结束之后,窗口会滑动到15s~45s这个范围,然后在新的窗口中重新统计数据。这种方式很好地解决了固定窗口算法的临界值问题。
Sentinel就是采用滑动窗口算法来实现限流的,后续在源码分析部分会再讲到
2.3令牌桶限流算法
令牌桶是网络流量整形(TrafficShaping)和速率限制(Rate Limiting)中最常使用的-种算法。对于每一个请求,都需要从令牌桶中获得一个令牌,如果没有获得令牌,则需要触发限流策略。
如图7-5所示,系统会以一个恒定速度(r tokens/sec)往固定容量的令牌桶中放入令牌,如果此时有客户端请求过来,则需要先从令牌桶中拿到令牌以获得访问资格
假设令牌生成速度是每秒10个,也就等同于QPS=10,此时在请求获取令牌的时候,会存在三种情况
- 请求速度大于令牌生成速度:那么令牌会很快被取完,后续再进来的请求会被限流。
- 请求速度等于令牌生成速度:此时流量处于平稳状态。
- 请求速度小于令牌生成速度:说明此时系统的并发数并不高,请求能被正常处理
由于令牌桶有固定的大小,当请求速度小于令牌生成速度时,令牌桶会被填满。所以令牌桶能够处理突发流也就是在短时间内新增的流量系统能够正常处理,这是令牌桶的特性。
2.4 漏桶限流算法
漏桶限流算法的主要作用是控制数据注入网络的速度,平滑网络上的突发流量。
漏桶限流算法的原理如图7-6所示,在漏桶算法内部同样维护一个容器,这个容器会以恒定速度出水,不管上面的水流速度多快,漏桶水滴的流出速度始终保持不变。实际上消息中间件就使用了漏桶限流的思想,不管生产者的请求量有多大,消息的处理能力取决于消费者。
在漏桶限流算法中,存在以下几种可能的情况
- 请求速度大于漏桶流出水滴的速度:也就是请求数超出当前服务所能处理的极限,将会触发限流策
略。 - 请求速度小于或者等于漏桶流出水滴的速度,也就是服务端的处理能力正好满足客户端的请求量将正常执行。
漏桶限流算法和令牌桶限流算法的实现原理相差不大,最大的区别是漏桶无法处理短时间内的突发流量
漏桶限流算法是一种恒定速度的限流算法。
三、服务熔断与降级
在微服务架构中,由于服务拆分粒度较细,会出现请求链路较长的情况。如图7-7所示,用户发起一个请求操作,需要调用多个微服务才能完成;
在高并发场景中,这些依赖服务的稳定性对系统的影响非常大,比如某个服务因为网络延迟或者请求超时等原因不可用时,就会导致当前请求阻塞,如图7-8所示,一旦某个链路上被依赖的服务不可用,很可能出现请求堆积从而导致出现雪崩效应。
3.1 服务熔断
所以,服务熔断就是用来解决这个问题的方案。服务熔断是指当某个服务提供者无法正常为服务调用者提供服务时,比如请求超时、服务异常等,为了防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,当触发熔断之后,后续一段时间内该服务调用者的请求都会直接失败,直到目标服务恢复正常。
3.2服务降级需要有一个参考指标
服务降级需要有一个参考指标,一般来说有以下几种常见方案
- 平均响应时间:比如1s内持续进入5个请求,对应时刻的平均响应时间均超过闻值,那么接下来在一个固定的时间窗口内,对这个方法的访问都会自动熔断。
- 异常比例:当某个方法每秒调用所获得的异常总数的比例超过设定的闻值时,该资源会自动进入降级状态,也就是在接下来的一个固定时间窗口中,对这个方法的调用都会自动返回
- 异常数量:和异常比例类似,当某个方法在指定时间窗口内获得的异常数量超过闯值时会触发熔断。
Sentinel也提供了熔断功能,在后续的章节中我们会演示如何通过Sentinel实现服务熔断
四 分布式限流架Sentinel
Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性。
在阿里巴巴内部有一句口号:“稳定压倒一切”,稳定性是系统的基础能力,稳定性差的系统会出现服务超时或服务不可用,给用户带来不好的体验,从而对业务造成恶劣影响。所以系统稳定性是一条“红线”,任何业务需求或技术架构升级都不应该越过它。
目前,Sentinel在阿里内部被广泛使用,为多年双11、双12、年货节618等大促活动保驾护航,并Sentinel开源以后也被很多互联网企业采用。
4.1 Sentinel的特性
如图7-9所示,Sentinel的特性非常多
- 丰富的应用场景:几乎涵盖所有的应用场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制等。
- 实时监控:Sentinel提供了实时监控功能。开发者可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群汇总运行情况。
- 开源生态支持:Sentinel提供开箱即用的与其他开源框架/库的整合,例如与Spring CloudDubbogRPC的整合。开发者只需要引入相应的依赖并进行简单的配置即可快速接入Sentinel。
- SPI扩展点支持:Sentine提供了SPI扩展点支持,开发者可以通过扩展点来定制化限流规则,动态数据源适配等需求
4.2 Sentinel的组成
Sentinel分为两个部分:
- 核心库(Java客户端):不依赖任何框架/库,能够运行于所有Jva运行时环境,同时对Dubbo、SpringCloud等框架也有较好的支持。
- 控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
4.3 SentinelDashboard的部署
Sentinel提供一个轻量级的开源控制台,它支持机器发现,以及健康情况管理、监控(单机和集群)、规则管理和推送的功能。
SentinelDashboard的安装步骤如下
- 在GitHub中Sentinel的源码仓库中:
- 直接下载源码通过mvnclean package自己构建
- 直接在Release页面下载已经构建好的Jar
- 通过以下命令启动控制台:
java-Dserver.port=7777 -Dcsp,sentinel.dashboard.server=localhost;7777 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中,启动参数的含义如下。
-Dserver.port:指定Sentinel控制台的访问端口,默认是8080
-Dcsp.sentineldashboard.server:指定Sentinel Dashboard控制台的IP地址和端口,这里进行设置的目的是把自己的限流数据暴露到监控平台。
-Dprojectname:设置项目名称
4.4Sentinel的基本应用
使用Sentinel的核心库来实现限流,主要分以下几个步骤。
- 定义资源
- 定义限流规则。
- 检验规则是否生效
所谓的资源,就是需要通过限流保护的最基本元素,比如一个方法。有了需要保护的资源之后,就可以针对该资源设置流量控制规则了。
下面先通过一个简单的案例来演示一下Sentinel的基本使用方法,让读者对Sentinel有一个基本的认识。
4.4.1Sentinel实现限流首先,引入Sentinel的核心库:
<dependency><groupId>com.alibaba,csp</groupId><artifactId>sentinel-core</artifactId><version>1.7.1</version>
</dependency>
然后,定义一个普通的业务方法
在doSomething方法中,通过使用Sentinel中的SphU.entry(“doSomething”)定义一个资源来实现流控的逻辑,它表示当请求进入doSomething方法时,需要进行限流判断。如果抛出BlockException异常,则表示触发了限流。接着,针对该保护的资源定义限流规则:
针对资源doSomething,通过initFlowRules设置限流规则,其中参数的含义如下
- Grade:限流闻值类型,QPS模式(1)或并发线程数模式(0)。
- count:限流闻值
- resource:设置需要保护的资源。这个资源的名称必须和SphU.entry中使用的名称保持一致上述代码的意思是,针对doSomething方法,每秒最多允许通过20个请求,也就是QPS为20最后,通过main方法进行测试
Sentinel 限流日志查看
从日志中可以看出,这个程序每秒稳定输出(doSomething)20次,和规则中预先设定的闻值是一样的,而被拒绝的请求每秒最高达50多万次。