Spring Cloud Alibaba 服务熔断和限流
前言:为什么需要流控降级我们的生产环境经常会出现一些不稳定的情况,如:大促时瞬间洪峰流量导致系统超出最大负载,load 飙高,系统崩溃导致用户无法下单“黑马”热点商品击穿缓存,DB 被打垮,挤占正常流量调用端被不稳定服务拖垮,线程池被占满,导致整个调用链路卡死这些不稳定的场景可能会导致严重后果。大家可能想问:如何做到均匀平滑的用户访问?如何预防流量过大或服务不稳定带来的影响?这时候我们就要请出微服
前言:为什么需要流控降级
我们的生产环境经常会出现一些不稳定的情况,如:
- 大促时瞬间洪峰流量导致系统超出最大负载,load 飙高,系统崩溃导致用户无法下单
- “黑马”热点商品击穿缓存,DB 被打垮,挤占正常流量
- 调用端被不稳定服务拖垮,线程池被占满,导致整个调用链路卡死
这些不稳定的场景可能会导致严重后果。大家可能想问:如何做到均匀平滑的用户访问?如何预防流量过大或服务不稳定带来的影响?这时候我们就要请出微服务稳定性的法宝 —— 高可用流量防护,其中重要的手段就是流量控制和熔断降级,它们是保障微服务稳定性重要的一环。
为什么需要流量控制?
流量是非常随机性的、不可预测的。前一秒可能还风平浪静,后一秒可能就出现流量洪峰了(例如双十一零点的场景)。然而我们系统的容量总是有限的,如果突然而来的流量超过了系统的承受能力,就可能会导致请求处理不过来,堆积的请求处理缓慢,CPU/Load 飙高,最后导致系统崩溃。因此,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量控制。
为什么需要熔断降级?
一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。
Sentinel: 高可用护航的利器
Sentinel 是阿里巴巴开源的,面向分布式服务架构的高可用防护组件,主要以流量为切入点,从流量控制、流量整形、熔断降级、系统自适应保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀、冷启动、消息削峰填谷、自适应流量控制、实时熔断下游不可用服务等,是保障微服务高可用的利器,原生支持 Java/Go/C++ 等多种语言,并且提供 Istio/Envoy 全局流控支持来为 Service Mesh 提供高可用防护的能力。
Sentinel 的技术亮点:
- 高度可扩展能力:基础核心 + SPI 接口扩展能力,用户可以方便地扩展流控、通信、监控等功能
- 多样化的流量控制策略(资源粒度、调用关系、流控指标、流控效果等多个维度),提供分布式集群流控的能力
- 热点流量探测和防护
- 对不稳定服务进行熔断降级和隔离
- 全局维度的系统负载自适应保护,根据系统水位实时调节流量
- 覆盖 API Gateway 场景,为Spring Cloud Gateway、Zuul 提供网关流量控制的能力
- 实时监控和规则动态配置管理能力
一些普遍的使用场景:
-
在服务提供方(Service
Provider)的场景下,我们需要保护服务提供方自身不被流量洪峰打垮。这时候通常根据服务提供方的服务能力进行流量控制,或针对特定的服务调用方进行限制。我们可以结合前期压测评估核心接口的承受能力,配置QPS 模式的限流,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。 -
为了避免调用其他服务时被不稳定的服务拖垮自身,我们需要在服务调用端(Service
Consumer)对不稳定服务依赖进行隔离和熔断。手段包括信号量隔离、异常比例降级、RT 降级等多种手段。 -
当系统长期处于低水位的情况下,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。这时候我们可以借助 Sentinel 的 WarmUp 流控模式控制通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,而不是在一瞬间全部放行。这样可以给冷系统一个预热的时间,避免冷系统被压垮
-
利用 Sentinel 的匀速排队模式进行“削峰填谷”,把请求突刺均摊到一段时间内,让系统负载保持在请求处理水位之内,同时尽可能地处理更多请求
-
利用 Sentinel 的网关流控特性,在网关入口处进行流量防护,或限制 API 的调用频率。
Sentinel有着丰富的开源生态。Sentinel 开源不久就被纳入 CNCF Landscape 版图,并且也成为 Spring Cloud官方推荐的流控降级组件之一。社区提供 Spring Cloud、Dubbo、gRPC、Quarkus
等常用微服务框架的适配,开箱即用;同时支持 Reactive 生态,支持 Reactor、Spring WebFlux
异步响应式架构。Sentinel 也在逐渐覆盖 API Gateway 和 Service Mesh 场景,在云原生架构中发挥更大的作用。
在原来的 Spring Cloud Netflix 系列中,有自带的熔断组件 Hystrix ,是 Netflix 公司提供的一个开源的组件,提供了熔断、隔离的这些特性,不过 Hystrix 在 2018 年 11 月份开始,就不再迭代开发,进入维护的模式。同年开源的 Spring Cloud Alibaba (SCA) 提供了一站式的解决方案,默认为 Sentinel 整合了 Spring Web、RestTemplate、FeignClient 和 Spring WebFlux。Sentinel 在 Spring Cloud 生态中,不仅补全了 Hystrix 在 Servlet、RestTemplate 和 API Gateway 这一块的空白,而且还完全兼容了 Hystrix 在 FeignClient 中限流降级的用法,并且支持运行时灵活地配置和调整限流降级规则。同时 SCA 还集成了 Sentinel 提供的 API gateway 流控模块,可以无缝支持 Spring Cloud Gateway 和 Zuul 网关的流控降级。
Spring Cloud Alibaba Sentinel 服务限流/熔断实战
下面到了动手时间了!
我们已经将案例代码集成在沙箱中,通过点击链接直接访问。
我们结合一个实例来对 Spring Cloud 服务限流/熔断进行实战。我们的实例项目由四个模块构成:
service-api: 服务接口定义,供 consumer/provider 引用
dubbo-provider: Dubbo 服务端,对外提供一些服务
web-api-demo: Spring Boot Web 应用,其中的一些 API 会作为 consumer 来调用 dubbo-provider 获取结果。里面一共定义了三个 API path:
/demo/hello: 接受一个 name 参数,会 RPC 调用后端的 FooService:sayHello(name) 方法。
/demo/time:调用后端的 FooService:getCurrentTime 方法获取当前时间;里面可以通过 slow 请求参数模拟慢调用。
/demo/bonjour/{name}: 直接调用本地 DemoService 服务。
demo-gateway: Spring Cloud Gateway 网关,作为整个项目的访问入口,将流量转发至后端服务或第三方服务。我们的入口 URL 访问都会经过该 API gateway。demo-gateway 的路由配置如下:
spring:
cloud:
gateway:
enabled: true
discovery:
locator:
# route ID 转化小写
lower-case-service-id: true
routes:
- id: foo-service-route
uri: http://localhost:9669/
predicates:
- Path=/demo/**
- id: httpbin-route
uri: https://httpbin.org
predicates:
- Path=/httpbin/**
filters:
- RewritePath=/httpbin/(?<segment>.*), /$\{segment}
这个路由配置包含两个路由:
foo-service-route: 会将 /demo/ 开头的访问路由到 localhost:9669 后端服务上面,即对应我们的 Web 服务。我们访问示例中的 API 都会经过这个路由,比如 localhost:8090/demo/time。
httpbin-route: 这是一个示例路由,它会将 /httpbin/ 开头的访问路由到 https://httpbin.org 这个示例网站上,比如 localhost:8090/httpbin/json 实际会映射到 https://httpbin.org/json 上面。
同时我们的环境也包含启动好的 Sentinel 控制台,可以直接访问并供各个服务接入。对应的地址:http://139.196.203.133:8080
下面我们来一步一步操作接入 SCA Sentinel 并通过控制台/Nacos 动态数据源配置流控降级规则来验证效果。
spring-cloud-alibaba-dependencies 配置
首先第一步我们在项目的父 pom 里面导入最新版本的 spring-cloud-alibaba-dependencies,这样我们在实际引入 SCA 相关依赖的时候就不需要指定版本号了:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
服务接入 SCA Sentinel
首先我们分别为三个服务模块引入 Spring Cloud Alibaba Sentinel 依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
starter 会自动对 Sentinel 的适配模块进行配置,只需要简单的配置即可快速接入 Sentinel 并连接到 Sentinel 控制台。
对于 Dubbo 服务,我们还需要额外引入 Dubbo 的适配模块。Sentinel 为 Apache Dubbo 提供开箱即用的整合模块,仅需引入 sentinel-apache-dubbo-adapter 依赖即可接入 Dubbo 自动埋点统计(支持 provider 和 consumer):
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
<version>1.8.0</version>
</dependency>
我们在 web-api-demo 和 dubbo-provider 两个应用的 pom 文件添加 adapter 依赖,这样两个应用的 Dubbo consumer/provider 接口就可以自动被 Sentinel 统计。
对于 Spring Cloud Gateway、Zuul 1.x 等网关,我们还需要在上面 SCA 依赖的基础上额外引入 spring-cloud-alibaba-sentinel-gateway 依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
这个依赖会自动为网关添加 Sentinel 相关的配置,从而可以让 API gateway 自动接入 Sentinel。我们在 demo-gateway 应用的 pom 文件里面添加这个依赖,这样我们的 gateway 应用就可以接入 Sentinel 了。
引入依赖之后,我们只需要进行简单的配置即可快速接入 Sentinel 控制台。我们可以在 application.properties 文件里面配置应用名和连接控制台的地址,以 web-api-demo 为例:
spring.application.name=foo-web
spring.cloud.sentinel.transport.dashboard=localhost:8080
其中 spring.application.name 相信大家都比较熟悉了,这里 Spring Cloud Alibaba Sentinel 会自动提取这个值作为接入应用的 appName。同时我们通过 spring.cloud.sentinel.transport.dashboard 来配置要连接的控制台地址和端口。
完成以上的配置后,我们可以依次启动 dubbo-provider、web-api-demo 和 demo-gateway 应用,并通过网关入口访问 localhost:8090/demo/time 获取当前时间。触发服务后,我们稍后可以在 Sentinel 控制台看到我们的三个应用,可以在监控页面看到访问信息,代表接入成功。
我们可以在每个应用的簇点链路页面看到当前应用的一些埋点调用,比如 Web 应用可以看到 Web URL 和 Dubbo consumer 调用:
流控规则
下面我们来配一条最简单的流控规则。在 Dubbo provider 端,我们进入簇点链路页面,针对 com.alibaba.csp.sentinel.demo.dubbo.FooService:getCurrentTime(boolean) 这个服务调用配置限流规则(需要有过访问量才能看到)。我们配一条 QPS 为 1 的流控规则,这代表针对该服务方法的调用每秒钟不能超过 1 次,超出会直接拒绝。
点击“新增”按钮,成功添加规则。我们可以在浏览器反复请求 localhost:8090/demo/time(频率不要太慢),可以看到会出现限流异常信息(Dubbo provider 默认的限流处理逻辑是抛出异常,该异常信息由 Dubbo 直接返回,并由 Spring 展示为默认 error 页面):
同时我们也可以在“实时监控”页面看到实时的访问量和拒绝量:
我们同样也可以在 Web API 处配置限流规则,观察效果。Spring Web 默认的限流处理逻辑是返回默认的提示信息(Blocked by Sentinel),状态码为 429。在后面的章节我们会介绍如何自定义流控处理逻辑。
了解了限流的基本用法,大家可能想问:生产环境我需要针对每个接口都去配置流控规则吗?阈值不会配怎么办?其实,限流降级的配置是需要结合容量规划、依赖梳理来做的。我们可以借助 JMeter 或 阿里云 PTS 等压测工具对我们的服务进行全链路压测,了解每个服务的最大承受能力,来确定核心接口的最大容量并作为 QPS 阈值。
网关流控规则
Sentinel 对 API Gateway 流控的场景进行了定制,支持针对网关的路由(如上面 gateway 定义的 foo-service-route)或自定义的 API 分组进行流控,支持针对请求属性(如某个 header)进行流控。用户可以在 Sentinel 控制台 自定义 API 分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
下面我们在控制台针对 gateway 配置一条网关流控规则。我们可以看到 API Gateway 的控制台页面与普通应用的页面有一些差异,这些就是针对网关场景的定制。Sentinel 网关流控规则支持提取某个 route 的请求属性,包括 remote IP、header、URL 参数、cookie 等,支持自动统计其中的热点值并分别进行限制,也支持针对某个具体值进行限制(比如给某个 uid 限量)。
我们给 foo-service-route 这个路由配一条针对请求属性的网关流控规则。这条规则会针对 URL 参数中提取出来的每个热点 uid 参数分别进行限制,每分钟的请求量最多允许 2 次。
保存规则后,我们可以构造一些向后端服务的请求,携带上不同的 uid 参数(即使没有用到),比如 localhost:8090/demo/time?uid=xxx。我们可以观察到,每个 uid 的访问每分钟超出两次后会出现限流页面。
关于 Sentinel 网关流控的详细配置指南和实现原理请参考 网关流控文档。
熔断降级规则
熔断降级通常用于自动切断不稳定的服务,防止调用方被拖垮导致级联故障。熔断降级规则通常在调用端,针对弱依赖调用进行配置,在熔断时返回预定义好的 fallback 值,这样可以保证核心链路不被不稳定的旁路影响。
Sentinel 提供以下几种熔断策略:
- 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用
RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs,默认为1s)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN
状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。 - 异常比例(ERROR_RATIO):当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN
状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0%
-100%。 - 异常数(ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
下面我们来在 Web 应用中针对 Dubbo consumer 来配置慢调用熔断规则,并模拟慢调用来观察效果。我们在 web-api-demo 中针对 com.alibaba.csp.sentinel.demo.dubbo.FooService 服务调用配置熔断降级规则。
控制台配置的统计时长默认为 1s。在上面的这条规则中,我们设定慢调用临界值为 50ms,响应时间超出 50ms 即记为慢调用。当统计时长内的请求数 >=5 且慢调用的比例超出我们配置的阈值(80%)就会触发熔断,熔断时长为 5s,经过熔断时长后会允许一个请求探测通过,若请求正常则恢复,否则继续熔断。
我们的实例中 /demo/time API 可以通过 slow 请求参数模拟慢调用,当 slow=true 时该请求耗时会超过 100ms。我们可以用 ab 等压测工具或脚本,批量请求 localhost:8090/demo/time?slow=true,可以观察到熔断的返回
如果我们一直模拟慢调用,我们可以观察到熔断后每 5s 会允许通过一个请求,但该请求仍然是慢调用,会重新打回熔断,无法恢复。我们可以在触发熔断后,等待一段时间后手动发一个不带 slow=true 的正常请求,然后再进行请求,可以观察到熔断恢复。
需要注意的是,即使服务调用方引入了熔断降级机制,我们还是需要在 HTTP 或 RPC 客户端配置请求超时时间,来做一个兜底的防护。
注解方式自定义埋点
刚才我们看到的埋点都是 Sentinel 适配模块提供的自动埋点。有的时候自动埋点可能没法满足我们的需求,我们希望在某个业务逻辑的位置进行限流,能不能做到呢?当然可以!Sentinel 提供两种方式进行自定义埋点:SphU API 和 @SentinelResource 注解,前者最为通用但是代码比较繁杂,耦合度较高;注解方式侵入性较低,但有使用场景的限制。这里我们来动手在 Web 应用的 DemoService 上添加注解,来达到针对本地服务埋点的目标。
在 DemoService 中我们实现了一个简单的打招呼的服务:
@Service
public class DemoService {
public String bonjour(String name) {
return "Bonjour, " + name;
}
}
下面我们给 bonjour 这个函数添加 @SentinelResource 注解,注解的 value 代表这个埋点的名称(resourceName),会显示在簇点链路/监控页面。
@SentinelResource(value = "DemoService#bonjour")
public String bonjour(String name)
加上该注解后,再通过网关访问 /demo/bonjour/{name} 这个 API 的时候,我们就可以在簇点链路页面看到我们自定义的 DemoService#bonjour 埋点了。
添加注解埋点只是第一步。一般在生产环境中,我们希望在这些自定义埋点发生限流的时候,有一些 fallback 逻辑,而不是直接对外抛出异常。这里我们可以写一个 fallback 函数:
public String bonjourFallback(Throwable t) {
if (BlockException.isBlockException(t)) {
return "Blocked by Sentinel: " + t.getClass().getSimpleName();
}
return "Oops, failed: " + t.getClass().getCanonicalName();
}
我们的 fallback 函数接受一个 Throwable 参数,可以从中获取异常信息。Sentinel 注解的 fallback 会捕获业务异常和流控异常(即 BlockException 及其子类),我们可以在 fallback 逻辑里面进行相应的处理(如日志记录),并返回 fallback 的值。
注意:Sentinel 注解对 fallback 和 blockHandler 函数的方法签名有要求,具体请参考此处文档。
写好 fallback 函数的实现后,我们在 @SentinelResource 注解里面指定一下:
@SentinelResource(value = "DemoService#bonjour", defaultFallback
= "bonjourFallback") public String bonjour(String name)
这样当我们自定义的 DemoService#bonjour 资源被限流或熔断的时候,请求会走到 fallback 的逻辑中,返回 fallback 结果,而不会直接抛出异常。我们可以配一个 QPS=1 的限流规则,然后快速请求后观察返回值:
? ~ curl http://localhost:8090/demo/bonjour/Sentinel
Bonjour, Sentinel
? ~ curl http://localhost:8090/demo/bonjour/Sentinel
Blocked by Sentinel: FlowException
注意:使用 @SentinelResource 注解要求对应的类必须由 Spring 托管(即为 Spring bean),并且不能是内部调用(没法走到代理),不能是 private 方法。Sentinel 注解生效依赖 Spring AOP 动态代理机制。
配置自定义的流控处理逻辑
Sentinel 的各种适配方式均支持自定义的流控处理逻辑。以 Spring Web 适配为例,我们只需要提供自定义的 BlockExceptionHandler 实现并注册为 bean 即可为 Web 埋点提供自定义处理逻辑。其中 BlockExceptionHandler 的定义如下:
public interface BlockExceptionHandler {
// 在此处处理限流异常,可以跳转到指定页面或返回指定的内容
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
我们的 Web 应用中提供了 Web 埋点自定义流控处理逻辑的示例:
@Configuration
public class SentinelWebConfig {
@Bean
public BlockExceptionHandler sentinelBlockExceptionHandler() {
return (request, response, e) -> {
// 429 Too Many Requests
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Oops, blocked by Sentinel: " + e.getClass().getSimpleName());
out.flush();
out.close();
};
}
}
该 handler 会获取流控类型并打印返回信息,返回状态码为 429。我们可以根据实际的业务需求,配置跳转或自定义的返回信息。
对于注解方式,我们上一节已经提到,可以指定 fallback 函数来处理流控异常和业务异常,这里不再展开讲解;对于 Dubbo 适配,我们可以通过 DubboAdapterGlobalConfig 注册 provider/consumer fallback 来提供自定义的流控处理逻辑;对于 Spring Cloud Gateway 适配,我们可以注册自定义的 BlockRequestHandler 实现类来为网关流控注册自定义的处理逻辑。
对 Spring Cloud 其他组件的支持
Spring Cloud Alibaba Sentinel 还提供对 Spring Cloud 其它常用组件的支持,包括 RestTemplate、Feign 等。篇幅所限,我们不展开实践。大家可以参考 Spring Cloud Alibaba 文档 来进行接入和配置。
如何选择流控降级组件
讲到这里,大家可能会有疑问:Sentinel 和其它同类产品(如 Hystrix)相比有什么优缺点?是否有必要迁移到 Sentinel?如何快速迁移?以下是 Sentinel 与其它 fault-tolerance 组件的对比:
总结
通过本教程,我们了解了流控降级作为高可用防护手段的重要性,了解了 Sentinel 的核心特性和原理,并通过动手实践学习了如何快速接入 SCA Sentinel 来为微服务进行流控降级。Sentinel 还有着非常多的高级特性等着大家去发掘,如热点防护、集群流控等,大家可以参考 Sentinel 官方文档来了解更多的特性和场景。
那么是不是服务的量级很小就不用进行限流防护了呢?是不是微服务的架构比较简单就不用引入熔断保护机制了呢?其实,这与请求的量级、架构的复杂程度无关。很多时候,可能正是一个非常边缘的服务出现故障而导致整体业务受影响,造成巨大损失。我们需要具有面向失败设计的意识,在平时就做好容量规划和强弱依赖的梳理,合理地配置流控降级规则,做好事前防护,而不是在线上出现问题以后再进行补救。
同时,我们也在阿里云上提供了 Sentinel 的企业版本 AHAS Sentinel,提供开箱即用的企业级高可用防护能力。与开源版本相比,AHAS 还提供以下的专业能力:
- 可靠的实时监控和历史秒级监控数据查询,包含接口维度的 QPS、响应时间及系统 load、CPU
使用率等指标,支持按照调用类型分类,支持同比/环比展示 - Top K 接口监控统计,快速了解系统的慢调用和大流量接口;热力图概览,快速定位不稳定的机器
- Java Agent 方式/K8s Java 应用零侵入快速接入,支持近 20 种主流框架和 API Gateway
- 全自动托管、高可用的集群流量控制
- Nginx 流量控制,支持规则动态配置、集群流控
更多推荐
所有评论(0)