在没有任何保护机制的情况下,如果所有的流量都进入服务器,很可能造成服务器宕机导致整个系统不可用,从而造成巨大的损失。为了保证系统在这些场景中仍然能够稳定运行,就需要采取一定的系统保护策略,常见的策略有服务降级、限流和熔断等。
限流的主要目的是通过限制并发访问数或者限制一个时间窗口内允许处理的请求数量来保护系统,一旦达到限制数量则对当前请求进行处理采取对应的拒绝策略,比如跳转到错误页面拒绝请求、进入排队系统、降级等。从本质上来说,限流的主要作用是损失一部分用户的可用性,为大部分用户提供稳定可靠的服务。
在实际开发过程中,限流几乎无处不在
计数器算法是一种比较简单的限流实现算法,在指定周期内累加访问次数,当访问次数达到设定的闻值时触发限流策略,当进入下一个时间周期时进行访问次数的清零
如图7-2所示,限定了每一分钟能够处理的总的请求数为100,在第一个一分钟内,一共请求了60次。接着到第二个一分钟,counter又从0开始计数,在一分半钟时,已经达到了最大限流的闯值,这个时候后续的所有请求都会被拒绝。这种算法可以用在短信发送的频次限制上,比如限制同一个用户一分钟之内触发短信发送的次数
这种算法存在一个临界问题,如图7-3所示,在第一分钟的0:58和第二分钟的1:02这个时间段内,分别出现了100个请求,整体来看就会出现4秒内总的请求量达到200,超出了设置的闻值。
为了解决计数器算法带来的临界问题,所以引入了滑动窗口算法。滑动窗口是一种流量控制技术,在TCP网络通信协议中,就采用了滑动窗口算法来解决网络拥塞的情况。
简单来说,滑动窗口算法的原理是在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的所有小时间窗口总的计数即可。
如图7-4所示,我们将一分钟拆分为4个小时间窗口,每个小时间窗口最多能够处理25个请求。并且通过虚线框表示滑动窗口的大小(当前窗口的大小是2,也就是在这个窗口内最多能够处理50个请求)。同时滑动窗口会随着时间往前移动,比如前面15s结束之后,窗口会滑动到15s~45s这个范围,然后在新的窗口中重新统计数据。这种方式很好地解决了固定窗口算法的临界值问题。
Sentinel就是采用滑动窗口算法来实现限流的,后续在源码分析部分会再讲到
令牌桶是网络流量整形(TrafficShaping)和速率限制(Rate Limiting)中最常使用的-种算法。对于每一个请求,都需要从令牌桶中获得一个令牌,如果没有获得令牌,则需要触发限流策略。
如图7-5所示,系统会以一个恒定速度(r tokens/sec)往固定容量的令牌桶中放入令牌,如果此时有客户端请求过来,则需要先从令牌桶中拿到令牌以获得访问资格
假设令牌生成速度是每秒10个,也就等同于QPS=10,此时在请求获取令牌的时候,会存在三种情况
由于令牌桶有固定的大小,当请求速度小于令牌生成速度时,令牌桶会被填满。所以令牌桶能够处理突发流也就是在短时间内新增的流量系统能够正常处理,这是令牌桶的特性。
漏桶限流算法的主要作用是控制数据注入网络的速度,平滑网络上的突发流量。
漏桶限流算法的原理如图7-6所示,在漏桶算法内部同样维护一个容器,这个容器会以恒定速度出水,不管上面的水流速度多快,漏桶水滴的流出速度始终保持不变。实际上消息中间件就使用了漏桶限流的思想,不管生产者的请求量有多大,消息的处理能力取决于消费者。
在漏桶限流算法中,存在以下几种可能的情况
漏桶限流算法和令牌桶限流算法的实现原理相差不大,最大的区别是漏桶无法处理短时间内的突发流量
漏桶限流算法是一种恒定速度的限流算法。
在微服务架构中,由于服务拆分粒度较细,会出现请求链路较长的情况。如图7-7所示,用户发起一个请求操作,需要调用多个微服务才能完成;
在高并发场景中,这些依赖服务的稳定性对系统的影响非常大,比如某个服务因为网络延迟或者请求超时等原因不可用时,就会导致当前请求阻塞,如图7-8所示,一旦某个链路上被依赖的服务不可用,很可能出现请求堆积从而导致出现雪崩效应。
所以,服务熔断就是用来解决这个问题的方案。服务熔断是指当某个服务提供者无法正常为服务调用者提供服务时,比如请求超时、服务异常等,为了防止整个系统出现雪崩效应,暂时将出现故障的接口隔离出来,断绝与外部接口的联系,当触发熔断之后,后续一段时间内该服务调用者的请求都会直接失败,直到目标服务恢复正常。
服务降级需要有一个参考指标,一般来说有以下几种常见方案
Sentinel也提供了熔断功能,在后续的章节中我们会演示如何通过Sentinel实现服务熔断
Sentinel是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、服务降级、系统负载保护等多个维度来帮助我们保障微服务的稳定性。
在阿里巴巴内部有一句口号:“稳定压倒一切”,稳定性是系统的基础能力,稳定性差的系统会出现服务超时或服务不可用,给用户带来不好的体验,从而对业务造成恶劣影响。所以系统稳定性是一条“红线”,任何业务需求或技术架构升级都不应该越过它。
目前,Sentinel在阿里内部被广泛使用,为多年双11、双12、年货节618等大促活动保驾护航,并Sentinel开源以后也被很多互联网企业采用。
如图7-9所示,Sentinel的特性非常多
Sentinel分为两个部分:
Sentinel提供一个轻量级的开源控制台,它支持机器发现,以及健康情况管理、监控(单机和集群)、规则管理和推送的功能。
SentinelDashboard的安装步骤如下
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:设置项目名称
使用Sentinel的核心库来实现限流,主要分以下几个步骤。
所谓的资源,就是需要通过限流保护的最基本元素,比如一个方法。有了需要保护的资源之后,就可以针对该资源设置流量控制规则了。
下面先通过一个简单的案例来演示一下Sentinel的基本使用方法,让读者对Sentinel有一个基本的认识。
<dependency>
<groupId>com.alibaba,cspgroupId>
<artifactId>sentinel-coreartifactId>
<version>1.7.1version>
dependency>
然后,定义一个普通的业务方法
在doSomething方法中,通过使用Sentinel中的SphU.entry(“doSomething”)定义一个资源来实现流控的逻辑,它表示当请求进入doSomething方法时,需要进行限流判断。如果抛出BlockException异常,则表示触发了限流。接着,针对该保护的资源定义限流规则:
针对资源doSomething,通过initFlowRules设置限流规则,其中参数的含义如下
从日志中可以看出,这个程序每秒稳定输出(doSomething)20次,和规则中预先设定的闻值是一样的,而被拒绝的请求每秒最高达50多万次。
Sentinel还可以使用@SentinelResource支持注解的方式来定义资源,具体实现方式如下:
Sentinel支持多种保护规则:流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则、热点参数规则。
限流规则在前面的案例中简单使用过,先通过FlowRule来定义限流规则,然后通过FlowRuleManagerloadRules来加载规则列表。完整的限流规则设置代码如下:
其中,FlowRule部分属性的含义说明如下。
Sentinel流量控制统计有两种类型,通过grade属性来控制:
并发线程数限流用来保护业务线程不被耗尽。比如,A服务调用B服务,而B服务因为某种原因导致服务不稳定或者响应延迟,那么对于A服务来说,它的吞吐量会下降,也意味着占用更多的线程(线程阻塞之后一直末释放),极端情况下会造成线程池耗尽
针对这种问题,一个常见的解决方案是通过不同业务逻辑使用不同的线程池来隔离业务自身的资源争抢问题,但是这个方案同样会造成线程数量过多带来的上下文切换问题。
Sentinel并发线程数限流就是统计当前请求的上下文线程数量,如果超出闻值,新的请求就会被拒绝。
QPS(Queries PerSecond)表示每秒的查询数,也就是一台服务器每秒能够响应的查询次数当QPS达到限流的闻值时,就会触发限流策略。
当QPS超过闻值时,就会触发流量控制行为,这种行为是通过controlBehavior来设置的,它包含:
直接拒绝是默认的流量控制方式,也就是请求流量超出闻值时,直接抛出一个FowException。
Warm Up是一种冷启动(预热)方式。当流量突然增大时,也就意味着系统从空闲状态突然切换到繁忙状态,有可能会瞬间把系统压垮。当我们希望请求处理的数量逐步递增,并在一个预期时间之后达到允许处理请求的最大值时,WarmUp就可以达到这个目的。
如图7-10所示,当前系统所能够处理的最大并发数是480,首先,在最下面标记的位置,系统一直处于空闲状态,接着请求量突然直线升高。这个时候系统并不是直接将QPS拉到最大值,而是在一定时间内逐步增加闻值,而中间这段时间就是一个系统逐步预热的过程
匀速排队的方式会严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,其实相当于前面讲的漏桶限流算法。
如图7-11所示,当QPS=2时,意味着每隔500ms才允许通过下一个请求。这种方式的好处是可以处理间隔性突发流量。
调用关系包括调用方和被调用方,一个方法又可能会调用其他方法,形成一个调用链。所谓的调用关系流量策略,就是根据不同的调用维度来发流量控制。
所谓调用方限流,就是根据请求来源进行流量控制,我们需要设置limitApp属性来设置来源信息,它有三个选项。
由于同一个资源可以配置多条规则,如果多个规则设置的limitApp不一样,那么规则的生效顺序为:{someorigin name}->other->default
一个被限流保护的方法,可能来自不同的调用链路。比如针对资源nodeA,入口Entrance1和入口Entrance2都调用了资源nodeA,那么Sentinel允许只根据某个入口来进行流量统计。比如我们针对nodeA资源,设置针对Entrance1入口的调用才会统计请求次数。它在一定程度上有点类似于调用方限流。
当两个资源之间存在依赖关系或者资源争抢时,我们就说这两个资源存在关联。这两个存在依赖关系的资源在执行时可能会因为某一个资源执行操作过于频繁而影响另外一个资源的执行效率**,所以关联流量控制(流控)就是限制其中一个资源的执行流量。**
Sentinel实现服务熔断操作的配置和限流类似,不同之处在于限流采用的是FlowRule,而熔断中采用的是DegradeRule,配置代码如下:
其中,有几个属性说明如下。
Sentinel提供三种熔断策略,对于不同策略,参数的含义也不相同
Sentinel默认统计的RT上限是4900ms,如果超出此闻值都会算作4900ms,如果需要修改,则通过启动参数-Dcspsentinelstatisticmaxrt=xxx来配置
至此,大家对于Sentinel应该有一个基本的认识了,在接下来的内容中,笔者会围绕Sentinel在SpringCloud生态下的使用进行展开,帮助读者加深对Sentinel的理解。
Spring Cloud Alibaba默认为Sentine整合了Servlet、RestTemplate、 FeignClient和Spring WebFlux。它不仅补全了Hystrix在Servlet和RestTemplate这一块的空白,而且还完全兼容了Hystrix在FeignClient中限流降级的用法,并支持灵活配置和调整流控规则。
下面主要演示Sentinel如何实现Spring Cloud应用的限流操作
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
<version>2.1.1.RELEASEversion>
dependency>
SPI是扩展点机制,如果需要被Sentinel加载,那么还要在resource目录下创建META-INF/services/comalibaba.csp.sentinelinit.InitFunc文件,文件内容就是自定义扩展点的全路径
com.gupaoedu.book.springcloud.sentinel.springcloudsentinelsample.FlowRuleInitFunc
按照上述配置好之后,在初次访问任意资源的时候,Sentinel就会自动加载hello资源的流控规则。
上述配置过程是基于手动配置来加载流控规则的,还有一种方式就是通过Sentinel Dashboard来进行配置
基于Sentinel Dashboard来配置流控规则,可以实现流控规则的动态配置,执行步骤如下
新增规则中的所有配置信息,实际就是FlowRule中对应的属性配置,为了演示效果,把单机闻值设置为1。
Blocked by Sentinel(flow limiting)
在默认情况下,URL触发限流后会直接返回
Blocked by Sentinel(flow limiting)
在实际应用中,大都采用JSON格式的数据,所以如果希望修改触发限流之后的返回结果形式,则可以通过自定义限流异常来处理,实现UrlBlockHandler并且重写blocked方法:
还有一种场景是,当触发限流之后,我们希望直接跳转到一个降级页面,可以通过下面这个配置来实现
spring.cloud.sentinel.servlet.block-page=(url)
Sentinel中HTTP服务的限流默认由Sentinel-Web-Servet包中的CommonFilter来实现,从代码中可以看到,这个Filter会把每个不同的URL都作为不同的资源来处理。
在下面这段代码中,提供了一个携带id)参数的REST风格API,对于每一个不同的id),URL也都不一样,所以在默认情况下Sentinel会把所有的URL当作资源来进行流控。
针对这个问题可以通过UrlCleaner接口来实现资源清洗,也就是对于/dean/{id};这个URL,我们可以统一归集到/clean/*资源下,具体配置代码如下,实现UrCleaner接口,并重写clean方法即可。
通过前面的案例可以发现,Sentinel的理念是只需要开发者关注资源的定义,它默认会对资源进行流控。当然,我们还需要对定义的资源设置流控规则,前面演示了两种方式:
针对第一种设置方式,如果接入Sentinel Dashboard,那么同样支持动态修改流控规则。但是,这里会存在一个问题,基于Sentinel Dashboard所配置的流控规则,都是保存在内存中的,一目应用重启,这些规则都会被清除。为了解决这个问题,Sentinel提供了动态数据源支持。
目前,Sentine支持Consul、ZooKeeper、Redis、Nacos、Apolloetcd等数据源的扩展。下面通过一个案例演示SpringCloud Sentinel集成Nacos实现动态流控规则,配置步骤如下
添加Nacos数据源的依赖包。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
<version>1.7.0version>
dependency>
在application.yml中添加数据源配置
部分配置说明如下。
登录Nacos控制台,创建流控配置规则,配置信息如图7-14所示。
通过上述配置整合之后,接口流控规则的动态修改就存在于以下两个地方
那么问题就来了,在Nacos控制台上修改流控规则,虽然可以同步到Sentinel Dashboard,但是Nacos此时应该作为一个流控规则的持久化平台,所以正常的操作过程应该是开发者在Sentinel Dashboard上修改流控规则后同步到Nacos上,遗憾的是,目前Sentinel Dashboard不支持该功能。
所以,Nacos名义上是“Datasource”,但实际上充当的仍然是配置中心的角色,开发者可以在Nacos控制台上动态修改流控规则并实现规则同步。在实际开发中,很难避免在不清楚情况的前提下,部分开发者通过Sentinel Dashboard来管理流控规则,部分开发者通过Nacos来管理流控规则,这将会导致非常严重的问题
如图7-16所示,Nacos在此处扮演的角色应该是一个“Datasource”,所以笔者强烈建议大家不要在Nacos上修改流控规则,因为这种修改的危险系数很高,毕竟Nacos的UI并不是专门负责流控规则维护的。
这也就意味着流控规则的管理应该集中在Sentinel Dashboard上,接下来的问题就很简单了,我们需要实现Sentinel Dashboard来动态维护流控规则并同步到Nacos上,目前官方还没有提供支持,但是大家可以自己来实现。
Sentinel Dashboard的“流控规则”下的所有操作,都会调用Sentinel-Dashboard源码中的FlowControllerV1类,这个类中包含流控规则本地化的CRUD操作。
另外,在comalibabacspsentineldashboard.controllerv2包下存在一个FlowControllerV2类这个类样提供流控规则的CRUD,和V1版本不同的是,它可以实现指定数据源的规则拉取和发布,部分代码如下:
修改SentinelDashboard的源码,具体的实现步骤如下
在GitHub中下载Sentinel Dashboard 171的源码
对于应用程序来说,需要改动的地方比较少,只要注意配置文件中data-id的命名要以-sentinel-flow结尾即可,因为在Sentinel Dashboard中我们写了一个固定的后缀
Sentinel提供了与Dubbo整合的模块Sentinel Apache Dubbo Adapter,可以针对服务提供方和服务消费方进行流控,在使用的时候,只需要添加以下依赖。
spring-coud-starter-alibaba-sentinel目前无法支持Dubbo服务的限流,所以针对Dubbo服务的限流只能使用sentinel-apache-dubbo-adapter。这个适配组件并没有自动接入Sentinel Dashboard,需要通过以下步骤来进行接入。
入sentinel-transport-simple-http依赖,这个依赖可以上报应用相关信息到控制台。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.7</version></dependency>
·添加启动参数
-Djava.net.preferIPv4Stack=true
-Dcsp.sentinel.api.port=8720Dcsp.sentinel.dashboard.server=192.168216128:7777
-Dproject.name=spring-cloud.sentinel-dubbo.provider
参数配置说明如下
登录SentinelDashboard之后,进入“簇点链路”,就可以看到如图7-18所示的资源信息
需要注意的是,限流可以通过服务接口或服务方法设置
Dubbo限流规则同样可以通过以下集中方式来实现
前面我们讲过基于Sentinel Dashboard来实现流控规则配置,最终持久化到Nacos中,然而规则的持久化机制在Spring Cloud Sentinel中是自动实现的,在Sentinel Apache Dubbo Adapter组件中并没有实现该功能。下面演示一下在Dubbo服务中如何实现规则的持久化。
热点数据表示经常访问的数据,在有些场景中我们希望针对这些访问频次非常高的数据进行限流,比如针对一段时间内频繁访问的用户IP地址进行限流,或者针对频繁访问的某个用户ID进行限流。
Sentinel提供了热点参数限流的策略,它是一种特殊的限流,在普通限流的基础上对同一个受保护的资源区根据请求中的参数分别处理,该策略只对包含热点参数的资源调用生效。热点限流在以下场景中使用较多。
Sentinel通过LRU策略结合滑动窗口机制来实现热点参数的统计,其中,LRU策略可以统计单位时间内最常访问的热点数据,滑动窗口机制可以协助统计每个参数的QPS
如图7-19所示,Sentinel会根据请求的参数来判哪些是热点参数,然后通过热点参数限流规则,将QSP超过设定闯值的请求阻塞。
针对不同的热点参数,需要通过SphU.entry(resourceName,EntryTypeIN,id)方法设置,其最后一个参数是一个数组,有多个热点参数时就按照次序依次传入,该配置表示后续会针对该参数进行热点限流
下面针对上述资源sayHelo设置热点参数限流规则,通过ParamFlowRuleManagerloadRules方法加载热点
Sentinel的核心分为三部分:工作流程、数据结构和限流算法,工作原理(整体架构)如图7-21所示
可以看出,调用链路是Sentinel的工作主流程,由各个Slot插槽组成,将不同的Slot按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起Sentinel中各个slot承担了不同的职责,例如LogSlot负责记录日志StatisticSlot负责统计指标数据、FowSlot负责限流等。这是一种职责分离的设计,每个模块更聚焦于实现某个功能。
在Sentinel中,所有的资源都对应一个资源名称(resourceName),每次访问该资源都会创建一个Entry对象,在创建Entry的同时,会创建一系列功能槽(Slot Chain),这些槽会组成一个责任链,每个槽负责不同的职责
在Spring Cloud中使用Sentinel实现限流的场景中,我们并不需要任何配置,Sentinel会自动保护所有的HTTP服务,本节重点讲解该实现机制。
在Spring-Cloud-Starter-Alibaba-Sentinel包中,我们知道Starter组件会用到自动装配,所以直接找到META-INF/spring.factories文件。
这里EnableAutoConfiguration自动装配了5个配置类
在SentinelWebAutoConfiguration配置类中,自动装配了一个FilterRegistrationBean,主要作用是注册个CommonFilter,并且默认情况下通过“/*”规则拦截所有的请求;
在本节中,主要针对Sentinel中的一些核心源码进行分析,帮助读者更好地理解Sentinel的实现原理Sentinel源码的版本为1.71,读者可以自行从GitHub上下载,源码模块如下。
限流及熔断的核心逻辑都是在sentinel-core中实现的,所以我们主要针对sentinel-core模块进行分析。
限流的判断逻辑是在Sphu.entry方法中实现的,这个方法往下执行最终会进入Sphentry(),Sph的默认实现是CtsPh。限流方法的主要逻辑是:
限流的核心是限流算法的实现,Sentinel默认采用滑动窗口算法来实现限流,具体的指标数据统计由StatisticSlot实现。
服务降级是通过DegradeSlot来实现的,它会根据用户配置的降级规则和系统运行时各个Node中的统计数据进行降级判断。
本章花了较长的篇幅讲解Sentinel的使用和原理,其中包括:
整体来看,Sentinel就两个部分,核心库和Sentinel Dashboard,核心库主要提供基础的限流及熔断的支持,而Sentinel Dashboard提供可视化监控及流控规则的管理。
同时,Sentinel核心库提供了非常丰富的应用场景,如秒杀场景中的突发流量控制、消息的削峰填谷、集群流量控制、熔断降级等。并且对主流的框架提供了很好的适配,使得开发者可以更容易地集成Sentinel。