熔断、限流、降级 —— SpringCloud Alibaba Sentinel
Sentinel 概述
Sentinel 是阿里中间件团队开源的,面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性
Sentinel 提供了两个服务组件:
- Sentinel 用来实现微服务系统中服务熔断、降级等功能
- Sentinel Dashboard 用来监控微服务系统中流量调用等情况
Sentinel 的两个核心概念是资源和规则:
- 资源:可以是 Java 应用程序中的任何内容,例如,资源可以是由应用程序提供的服务,或者是由应用程序调用的其他应用提供的服务,甚至可以是一段代码。只要是通过 SentinelAPI 定义的代码,就是资源,就能够被 Sentinel 保护
- 规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断和降级规则、系统保护规则,所有规则都可以动态实时调整
在资源限制的实现上,Sentinel 和 Hystrix 采用了完全不一样的方法。Hystrix 通过线程池隔离的方式对依赖进行隔离,这样做的好处是资源和资源之间做到了彻底隔离,缺点是增加了线程切换的成本,还需要预先对各个资源的线程池大小进行配置。Sentinel 对这个问题采用了如下两种手段:
- 通过并发线程数进行限制:Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其他资源的影响。这样不但没有线程切换的损耗,也不需要预先分配线程池的大小。在某个资源出现不稳定的情况时,例如响应时间变长,对资源的直接影响就是会造成线程数量逐步堆积。当线程在特定的资源上堆积到一定数量时,对该资源的新请求就会被拒绝。堆积的线程在完成任务后会继续接收请求
- 通过响应时间对资源进行降级:除了可以对并发线程的数量进行控制,Sentinel 还可以通过响应时间来快速降级不稳定的资源。在依赖的资源出现响应时间过长的情况时,对该资源的所有访问都会被直接拒绝,直到经过指定的时间窗口后才重新恢复
限流算法
限流的方式有很多,常用的有计数器、漏桶和令牌桶等
1. 计数器
采用计数器是一种比较简单的限流算法,一般会限制一秒钟能够通过的请求数。比如限流 QPS 为 100,算法的实现思路就是从第一个请求进来开始计时,在接下来的 1 秒内每来一个请求就把计数加 1,如果累加的数字达到了 100,后续的请求就会被全部拒绝。等到 1 秒结束后,把计数恢复成 0,重新开始计数。如果在单位时间 1 秒内的前 10 毫秒处理了 100 个请求,那么后面的 990 毫秒会请求拒绝所有的请求,我们把这种现象称为突刺现象
2. 漏桶算法
漏桶算法的思路很简单,一个固定容量的漏桶按照常量固定速率流出水滴。如果桶是空的,就不需要流出水滴。我们可以按照任意速率流入水滴到漏桶。如果流入的水滴超出了桶的容量,流入的水滴就会溢出(被丢弃),而漏桶容量是不变的。漏桶算法提供了一种机制,通过它可以让突发流量被整形,以便为网络提供稳定的流量
3. 令牌桶算法
令牌桶算法是比较常见的限流算法之一,可以使用它进行接口限流。令牌按固定的速率被放入令牌桶中,当桶装满时,新添加的令牌会被丢弃或拒绝。当请求到达时,将从桶中删除 1 个令牌。令牌桶中的令牌不仅可以被移除,还可以往里添加,所以为了保证接口随时有数据通过,必须不停地往桶里加令牌。由此可见,往桶里加令牌的速度决定了数据通过接口的速度。我们通过控制往令牌桶里加令牌的速度来控制接口的流量
4. 漏桶算法和令牌桶算法的区别
- 漏桶算法是按照常量固定选率流出请求的,流入请求速率任意,当流入的请求数累积到漏桶容量时,新流入的请求被拒绝
- 令牌桶算法是按照固定速率往桶中添加令牌的,请求是否被处理需要看桶中的令牌是否足够,当令牌数减为零时,拒绝新的请求
- 令牌桶算法允许突发请求,只要有令牌就可以处理,允许一定程度的突发流量
- 漏桶算法限制的是常量流出速率,从而使突发流入速率平滑
Sentinel Dashboard
Sentinel 提供一个轻量级的开源控制台,包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线
- 监控(单机和集群):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控
- 规则管理和推送:统一管理推送规则
- 鉴权:在生产环境中,鉴权非常重要,这里每个开发者需要根据自己的实际情况进行定制
从 GitHub 可以下载 Sentinel 安装包:https://github.com/alibaba/Sentinel/
下载得到的是一个 jar 包(sentinel-dashboard-1.8.6.jar),可以直接通过 Java 命令启动,如 java -jar 方式运行,默认端口为 8080,通过 http://localhost:8080/ 访问,用户名和密码默认是 sentinel
客户端接入控制台
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置文件配置如下:
spring:
cloud:
sentinel:
transport:
port: 18000 # 指定应用与sentinel控制台交互的端口
dashboard: localhost:8080 # sentinel后台地址
eager: true # 开启sentinel,默认开启
Sentinel 限流
Sentinel 流量控制的原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性
使用注解方式实现限流如下:
@Slf4j
@RestController
public class TestCon {
@GetMapping("/test/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public void byResource() {
log.info("按资源名称限流");
}
public void handleException(BlockException exception) {
log.error("触发失败回调方法", exception);
}
}
@SentinelResource
注解用于定义资源,可选属性如下:
-
value:指定资源名称
-
blockHandler / blockHandlerClass:指定处理 BlockExccption 异常函数名称。函数要求必须是 public,返回类型与原方法必须一致,函数参数类型需要和原方法相匹配并在最后加 BlockException 类型的参数,函数默认和原方法在同一个类中。若希望使用其他类的函数可配置 blockHandlerClass,并指定 blockHandlerClass 里面的方法,注意对应的函数必需为 static 函数,否则无法解析
// TestCon.java @GetMapping("/test/byResource") @SentinelResource(value = "byResource", blockHandler="handleBolckForTest", blockHandlerClass = {BlockHandlerClassTest}) public void byResource() { log.info("按资源名称限流"); } // BlockHandlerClassTest.java public static String handleBolckForTest(String name,int age, BlockException exception){ xxxxx }
-
fallback / fallbackClass:用于在抛出异常的时候提供 fallback 处理逻辑,可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理,返回值类型必须与原函数返回值类型一致,方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常,fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
在 Sentinel 控制台的流控规则中,新增流控规则,如图:
- 资源名:需要和
@SentineResource
注解的 value 属性值保持一致 - 针对来源:默认 default,表示对所有来源进行限流,有时候我们会希望根据上级微服务或者请求来源进行限流,可以根据自身的需求进行相应的配置
- 阈值类型:Sentinel 限流策略有两种统计类型,一种是统计并发线程数,另一种是统计 QPS
- Sentinel 会统计当前请求上下文的线程数量,如果超出阈值,新的请求会被立即拒绝
- 当 QPS 超过某个阈值的时候,采取措施进行流量控制,包括:
- 直接拒绝:默认的流量控制方式,当 QPS 超过任意规则的阈值,新的请求会被立即拒绝并抛出 FlowExccption
- Warm Up:预热/冷启动方式,在系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,通过冷启动让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
- 匀速排队:严格控制请求通过的间隔时间,即让请求以均匀的速度通过,对应的是漏桶算法
- 流控模式:
- 直接:接口达到限流条件时,开启限流
- 关联:当指定接口关联的接口达到限流条件时,开启对指定接口开启限流,举例:设置关联资源为 byResource2,那么当 byResource2 达到限流条件时,byResource 将不可用
- 链路:当从某个接口过来的资源达到限流条件时,开启限流,举例:有两个接口 getResource1 和 getResource2 能调用 byResource 资源,如果设置了 getResource1 并达到限流条件,那么将无法再通过 getResource1 调用 byResource,而 getResource2 不受影响
Sentinel 熔断降级
熔新和降级主要指在某个服务出现异常时,暂时切断不稳定的服务,避免因局部不稳定因素导致系统整体雪崩,Sentinel 提供了如下几种熔断策略:
- 慢调用比例(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
- 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
- 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断