Sentinel服务熔断和流控
简介
Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量 为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
Spring Cloud Alibaba Sentinel 同时兼具了熔断器和流控的功能。
Sentinel具有以下特征:
-
丰富的应用场景 : Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控
制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
-
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态 : Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、
gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
-
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
Spring的spi是通过ClassLoader去META-INF/spring.factories加载class,然后反射实例化返回。像SpringBoot用这种方式去加载一些自动配置类,即引入xx-starter就能够自动向spring容器中注入许多配置好的组件。
阿里云提供了 企业级的 Sentinel 服务,应用高可用服务 AHAS
Sentinel和Hystrix对比
https://github.com/alibaba/Sentinel/wiki/Sentinel-与-Hystrix-的对比
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
熔断
微服务架构的系统通常会包含多个微服务,各个微服务可能部署在不同的机器上并通过网络进行通信,那么就不可避免会遇到 "网络请求超时" 、"微服务不可用" 等问题,这就会进一步引起依赖它的微服务不可用,这样不断引发服务故障的现象称为『雪崩效应』,最终的结果是整个应用系统瘫痪。
为了解决上述问题,编程领域(参考现实生活)提出了熔断器 :
使用熔断器模式,如果请求出现异常,所有请求都会直接返回,而不会等待或阻塞,这样可以减少资源的浪费。
熔断器所造成的这种现象也叫『快速失败(fast fall)』。
流控
限流功能指的是 Sentinel(类似于过滤器、拦截器的效果)在收到请求后,拒绝请求的放行(至 Controller),而是直接返回,从而减少对 Controller,乃至 Service 的触发执行。
熔断和限流的区别在于,熔断是确确实实发生了错误,而限流是人为(根据设置)强行让一部分请求被打回。
sentinel安装
sentinel-dashboard 的下载安装
sentinel-dashboard 是基于 Spring Boot 开发的控制台。打包后可以直接运行,不需要额外的 Tomcat 等应用容器。Sentinel 控制台不仅能展示服务流控、熔断降级相关的数据,还可以通过配置的方式动态的为 Sentinel 客户端下发流量控制的指令
我们需要下载并安装的是 sentinel-dashBoard ,下载地址:https://github.com/alibaba/Sentinel/releases
注意:启动 sentinel-dashboard 需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台:
java
java -Dserver.port=8840 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
-
-Dserver.port=8840
用于指定 Sentinel 控制台端口为 8840。默认是 8080 。
-
-Dproject.name=sentinel-dashboard
指定 Sentinel 控制台程序的名称。
说明
如果你有多张网卡的话,你还需要指定使用哪张网卡(IP)来接受各个微服务上报的信息:
-Dcsp.sentinel.heartbeat.client.ip=192.168.xxx.xxx
访问网址:[http://127.0.0.1:8840]
从 1.6.0 起,sentinel-dashboard 引入基本的登录功能,默认用户名和密码都是 sentinel 。当然也可以通过 JVM 参数的方式进行修改
-
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为 sentinel ;
-
-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel;
-
-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;
Sentinel 本身就是一个 Spring Boot 应用,所以jar 包内部的 application.properties 文件也是可以修改配置的。
Sentinel实现限流
Spring Cloud Alibaba Sentinel 可以分别用在服务的 "请求发起方" 和 "请求被调方"
- 请求发起方使用的是 OpenFeign ,因此这种情况下 Sentinel 是和 OpenFeign 进行整合;
- 请求被调用使用的是 Spring MVC,因此这种情况下 Sentinel 是和 Spring MVC 进行整合
同时又由于 Sentinel 兼具熔断和流控两个功能,因此这里就有 4 种情况:
- 在服务发起方项目中,整合 OpenFeign 进行实现熔断功能;
- 在服务发起方项目中,整合 OpenFeign 进行实现限流功能;
- 在服务被调方项目中,整合 Spring MVC 进行实现熔断功能;
- 在服务被调方项目中,整合 Spring MVC 进行实现限流功能。
这样以来功能上就出现了重叠冗余,因此在实际使用中我们是这样安排的:
- 在服务发起方,Sentinel 整合 OpenFeign 实现熔断功能;
- 在服务被调方,Sentinel 整合 Spring MVC 实现限流功能。
总结:流控针对provider,熔断降级针对consumer
sentinel实现限流
回顾前面笔记中的 "关于 Sentinel 的使用方式" 章节,在这里,我们在服务的 "被调方" 使用 Sentinel 整合 Spring MVC 进行流量控制。
在这里,Sentinel 借助 Spring MVC 框架的 "拦截器" 机制整合进入 Spring MVC ,"抢先" 在 Controller 执行之前进行流控(和熔断)的判断,从而决定当前请求是否被放行至 Controller
sentinel整合mvc
1.引入相关依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 其实真正起作用的是被关联引入的 sentinel-spring-webmvc-adapter 包 -->
2.添加配置(连接到 sentinel-dashboard)
yaml
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 8719
feign:
sentinel:
enabled: true
3.访问 sentinel-dashboard
只需要完成上述的配置,代码不需要有任何的调整,我们就可以通过实时监控查看服务内的流量 QPS(每秒查询率) 以及平均响应时长等信息。
只有服务接口被访问的情况下,在 sentinel 里面才可以看到监控信息。这可能会让你『等』一段时间。
相关概念
- 上下文( Context )和 context-name
Context 代表调用链路上下文。是一个根节点,在整个调用链路的开始处,Sentinel 会创建上下文 Context 对象,并且为它指定一个 name ,相当于根资源。在 Sentinel 中,不同的调用链路可能使用同一个上下文 Context 对象(共一个根节点)。在这里( 和 Spring MVC 整合 ),我们的调用链路都是在 sentinel_spring_web_context 中:
- 资源(Resource)和 resource-name
在 Sentinel 中,对于每一份资源,Sentinel 会为赋予一个 name(或者你手动指定),和 Spring MVC 整合时,Sentinel 使用的是 URI 来作为 Controller 方法的资源名( 在这里,Controller 方法就是资源 )
流控规则
在菜单左侧的 簇点链路和流控规则都可以针对 服务接口添加流控规则:
当我们的服务接口资源被访问的时候,就会出现在 簇点链路 列表中,我们可以针对该服务接口资源配置流程控制规则
说明:
-
资源名:表示我们针对哪个接口资源进行流控规则配置,如:/test2/{id}
-
针对来源:表示针对哪一个服务访问当前接口资源的时候进行限流,default 表示不区分访问来源。如填写服务名称:xxx-service,表示 xxx-service 访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流,一般就是默认为default即可。
-
阈值类型/单机阈值:QPS,每秒钟请求数量。上图配置表示每秒钟超过2次请求的时候进行限流;当然我们可以设置线程数,表示开启 n 个线程处理资源请求,这个不是只每秒2个线程,对服务端 /test1请求,资源接口的 2 个线程都被占用的时候,其他访问失败!一般用的都是QPS
-
是否集群:默认情况下我们的限流策略都是针对单个服务的,当然sentinel 提供了集群限流的功能。
除非你的微服务规模特别大,一般不要使用集群模式。集群模式需要各节点与 token server 交互才可以,会增加网络交互次数,一定程度上会拖慢你的服务响应时间。
上面的限流规则用一句话说:对于任何来源的请求,当超过每秒 2 次的标准之后就直接限流,访问失败抛出 BlockException 异常!
流控规则高级选项
1.流控模式
- 直接:当前资源达到限流标准时就直接限流,默认值
- 关联:/important接口的重要程度要高于 /normal接口,如果,/important接口的访问压力很大,那么,可以『牺牲』掉 /normal` 接口,全力保证 /important 接口的正常运行
例如:我们在resttemplate-a微服务中,创建两个接口
java
@GetMapping("/query")
public String query(){
return "ok:query";
}
@GetMapping("/add")
public String add(){
return "ok:add";
}
先访问:http://localhost:9527/add和http://localhost:9527/query
我们对/query接口进行限流,这个配置的意思就是,当每秒对/add接口的请求超过2次时,就对/query接口进行限流,要注意这样是优先保证/add不限流,牺牲的是/query接口。10s内对/add接口超过20次请求,那么就对/query接口限流
测试:
- 链路
链路限流和关联限流的思路很像,假设我们要去请求某个微服务,该微服务有2个接口(/query和/add),而这两个接口又调用了同一个service层的方法(如:doSomething()方法),那么,我们可以『站在 doSomething的方法』的角度上进行设置:如果是 /query接口在调用service层的 doSomething方法,那么就进行限流,而 /add接口的调用就不限流,或设置为更宽松一些的流控
1.配置设置:通过配置关闭 sentinel 的 URL 收敛功能
yaml
spring:
application:
name: resttemplate-a
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8840
web-context-unify: false #默认是开启上下文整合,所有链路在根节点下,链路监控就是将请求分开统计
2.代码设计
java
controller层
@GetMapping("/query")
public String query(){
return userService.doSomething();
}
@GetMapping("/add")
public String add(){
return userService.doSomething();
}
//service层
@Component
public class UserService {
@SentinelResource("doSomething")
public String doSomething() {
return "hello world";
}
}
3.重新运行,先在界面发送请求 http://localhost:9527/query和http://localhost:9527/add,然后sentinel配置
链路配置:
4.测试:在jmeter对/add测试时不限流(如10秒钟对/add请求80次),但是同时在浏览器对/query测试时,每秒只能是2次请求。
2.流控效果
- 快速失败:很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP状态码:429 too many request),默认值
- Warm up:预热模式,也有叫冷启动,主要是为系统启动时设置预热时间,底层有预热因子是3,在系统刚启动时,使用的阈值不再是每秒多少个请求,而是设置的阈值除以预热因子,在预热的时间内,逐渐提升阈值,最后达到设置的阈值(也就是每秒多少个请求),好处是预防系统刚启动时,突发大量的请求,服务容易宕机。
1.代码设计
java
@GetMapping("/test/{id}")
public ResponseEntity<String> test1(@PathVariable Integer id){
return new ResponseEntity<>("ok", HttpStatus.OK);
}
2.配置设置
在10秒内,每秒允许的请求数会逐渐增加,也就是每秒的阈值逐渐提高
3.测试
- 排队等待:也叫流量整形,它让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的在队列排队等待一段时间,(即我们设置的时间,单位是毫秒),没有超过这个时间都能被及时处理,如果超过了这个等待时间针对请求的接口没有线程来处理,则抛出异常
测试:每秒50个请求
Sentinel 和 SpringMVC 整合原理
Sentinel 和 Spring MVC 的整合利用了 Spring MVC 的拦截器机制,Sentinel 实现了一个名为 SentinelWebInterceptor 的拦截器,其逻辑伪代码如下
java
public SentinelWebInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
try {
1. 初始化上下文;
2. 熔断、流控逻辑的判断,判断当前请求是否能继续执行;
return true; // 此时 Controller 方法会被调用。Controller 方法就是 3 。
} catch (BlockException e) {
4. 上述第 2 步未能通过,会抛出 BlockException ,表示请求被拒绝
return false;
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
if (发生了异常) {
5. 业务异常。记录、统计异常信息
}
6. 收尾工作:曾经创建的资源该回收的回收,该清除的清除
}
自定义限流异常信息返回
Sentinel 返回的默认信息是 Blocked by Sentinel (flow limiting),如果你对默认响应信息不满意,你可以自定义限流返回信息。
Sentinel 提供了 BlockExceptionHandler 接口。不管什么原因触发了 Sentinel 阻断用户的正常请求,Sentinel 都将『进入』到用户自定义的 BlockExceptionHandler 接口的实现类中,执行 handle 方法,并传入当前的请求、响应对象以及异常对象,并以 handle 方法的执行结果作为返回,回传给用户。
java
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
String msg = null;
if (ex instanceof FlowException) {
msg = "该请求限流了,请稍后重试";
} else if (ex instanceof DegradeException) {
msg = "被熔断了";
} else {
msg = "其它原因";
// ParamFlowException "热点参数限流";
// SystemBlockException "系统规则(负载/...不满足要求)";
// AuthorityException "授权规则不通过";
}
// http 状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(response.getWriter(), msg);
}
}
需要说明的是:不仅仅是因为限流和熔断这一个原因会导致 BlockExceptionhandler 的 handle 方法的执行,还有其它的原因也会调用这个handler方法,因此,需要对 handle 方法的 BlockException 参数对象进行 instanceof 判断
sentinel服务熔断降级
我们在服务的 "请求发起方" 使用 Sentinel 整合 OpenFeign 进行熔断降级
sentinel整合feign
1.添加依赖
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.添加配置(连接到 sentinel-dashboard)
yaml
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8840
feign:
sentinel:
enabled: true ##启用 Sentinel 与 OpenFeign 整合适配器
3.代码实现spring-service-a-feign微服务,调用spring-service-b微服务
java
@FeignClient(value = "spring-service-b",qualifier = "feignclient",
fallback = PermFeignClientImpl.class )
public interface PermFeignClient {
@GetMapping("/perms/{id}")
public Perms getPermsById(@PathVariable("id") Integer id);
}
=========降级代码==============
@Component
public class PermFeignClientImpl implements PermFeignClient {
@Override
public Perms getPermsById(Integer id) {
Perms perms = new Perms();
perms.setName("服务器忙,请稍后再试");
return perms;
}
}
===========controller=======================
@RestController
public class PermController {
@Autowired @Qualifier("feignclient")
private PermFeignClient permFeignClient;
@GetMapping("/perm")
public Perms getPermsById(Integer id) {
Perms perms = permFeignClient.getPermsById(id);
return perms;
}
}
spring-service-b微服务
java
@GetMapping("/perms/{id}")
public Perms getPermsById(@PathVariable("id") Integer id) {
if(id==1){
try {
Thread.sleep(800); //当id为1时,800毫秒才有相应
}catch (Exception ex){ }
}
if(id==2){
throw new RuntimeException();
}
return permService.getPermsById(id);
}
4.熔断规则
在 sentinel-dashboard 上你可以看到有 降级规则,它指的就是『设置当满足什么条件时,对服务进行降级』
慢调用比例
如下配置:在一秒内,发5次请求,如果每次请求的响应时间超过500毫秒,这种比例达到0.5(50%),就进行熔断,熔断时长就是10秒。如:1秒内有5次请求,其中有3次请求响应时间超过了500毫秒,那么这个比例就是60%,大于50%,此时就熔断,然后降级。
用jmeter测试,程序中当id=1时,每次响应都是800毫秒。所以每次的请求都大于500毫秒,失败率100%,这个时候去请求id=4的资源也是无法请求的,因为熔断了,所以也是直接降级。10s后再次请求id=4的就正常了。
异常数
如下配置:一秒内发送5次请求,如果有3次失败(异常),则直接熔断,然后降级
测试:localhost:9527/perm?id=2,结果5次都失败了,此时熔断时长是9秒,在这9秒内,如果去请求id=4的依然是降级,9s之后试着去发送id=4的请求,如果通过则进入闭合正常状态。
异常比例
如下配置:1秒钟发送5次请求,如果调用接口最终失败的比例超过了20%,则熔断9s。
yaml
#配置
spring:
cloud:
sentinel:
eager: true #取消Sentinel控制台懒加载
blockhandler fallback,blockhandler 优先级高
}catch (Exception ex){ }
}
if(id==2){
throw new RuntimeException();
}
return permService.getPermsById(id);
}
4.熔断规则
在 sentinel-dashboard 上你可以看到有 降级规则,它指的就是『**设置当满足什么条件时,对服务进行降级**』
[外链图片转存中...(img-QwfGPW1A-1694490781354)]
#### 慢调用比例
如下配置:在一秒内,发5次请求,如果每次请求的响应时间超过500毫秒,这种比例达到0.5(50%),就进行熔断,熔断时长就是10秒。如:1秒内有5次请求,其中有3次请求响应时间超过了500毫秒,那么这个比例就是60%,大于50%,此时就熔断,然后降级。
[外链图片转存中...(img-d74m5bKk-1694490781357)]
用jmeter测试,程序中当id=1时,每次响应都是800毫秒。所以每次的请求都大于500毫秒,失败率100%,这个时候去请求id=4的资源也是无法请求的,因为熔断了,所以也是直接降级。10s后再次请求id=4的就正常了。
#### 异常数
如下配置:一秒内发送5次请求,如果有3次失败(异常),则直接熔断,然后降级
[外链图片转存中...(img-URgCSfH7-1694490781358)]
测试:localhost:9527/perm?id=2,结果5次都失败了,此时熔断时长是9秒,在这9秒内,如果去请求id=4的依然是降级,9s之后试着去发送id=4的请求,如果通过则进入闭合正常状态。
#### 异常比例
如下配置:1秒钟发送5次请求,如果调用接口最终失败的比例超过了20%,则熔断9s。
[外链图片转存中...(img-bYCdjt00-1694490781358)]
```yaml
#配置
spring:
cloud:
sentinel:
eager: true #取消Sentinel控制台懒加载
```
blockhandler fallback,blockhandler 优先级高