Sentinel: 分布式系统的流量防卫兵
官方文档
1 Sentinel简介
1.1官方介绍
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500
台以下规模的集群的汇总运行情况。 - 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI
扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等
1.2 总结
Sentinel 是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断等手段达到保护系统的目的,通过服务降级增强服务被拒用户的体验。
2 服务降级
服务降级是一种增强用户体验的方式。当用户的请求由于各种原因被拒后,系统返回一个事先设定好的、用户可以接受的,但又令用户并不满意的结果。这种请求处理方式称为服 务降级。
案例完整源码
2.1 降级实现方式分类
对于 Sentinel,服务降级的实现方式根据消费者类型的不同,其支持两种方式:
- Sentinel 式降级:通过 Sentinel 自身的 API 实现的降级方式,适用于任意消费者类型。而根据降级方法应用范围、定义位置及可维护性的不同,又可分为两种:
方法级降级:降级方法与原方法定义在同一个类中,其仅是本类中的原方法可以使用。
类级降级:降级方法定义在专门的一个类中,其是一个可以被共享的降级类。该类中的所有方法均为降级方法,所以便于维护与管理。 - Feign 式降级:通过 OpenFeign 的 API 实现的降级方式,仅适用于 Feign 客户端的消费者类型。其只有类级降级方式。
2.2 Sentinel 式方法级降级
(1)添加依赖
在原有的feign-nacos项目中添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
(2) 修改处理器类
/**
* 定义了降级
*/
@SentinelResource(fallback = "getHandleFallback")
@GetMapping("/get/{id}")
public DepartVO getHandle(@PathVariable("id") int id) {
return departService.getDepartById(id);
}
public DepartVO getHandleFallback(int id, Throwable e) {
DepartVO departVO = new DepartVO();
departVO.setId(id);
departVO.setName("degrade-method-" + id + "-" + e.getMessage());
return departVO;
}
(3) 测试
- 只启动消费者,访问接口发现走了降级方法。
- 启动消费者和生产者无异常,正常访问接口
- 在消费者端模拟异常并启动,访问接口发现走了降级方法。
思考了一下,这里是为了模拟降级服务而简单的处理。实际中应该会具体实现降级业务,针对异常做些处理。
2.3 Sentinel 式类级降级
(1) 定义降级类
public class DepartServiceFallback {
public static boolean saveFallback(DepartVO depart, Throwable e) {
System.out.println("getHandle()执行异常 " + depart.toString());
return false;
}
public static List<DepartVO> listFallback() {
System.out.println("listHandle()执行异常 ");
DepartVO departVO = new DepartVO();
List<DepartVO> arrayList = new ArrayList<>();
departVO.setId(1);
departVO.setName("no any depart");
arrayList.add(departVO);
return arrayList;
}
}
(2) 修改 DepartController 处理器
/**
*@SentinelResource注解中指定降级方法和降级方法对应的类
*/
@SentinelResource(fallback = "saveFallback", fallbackClass = DepartServiceFallback.class)
@PostMapping("/save")
public boolean saveHandle(@RequestBody DepartVO depart) {
return departService.saveDepart(depart);
}
@SentinelResource(fallback = "listFallback", fallbackClass = DepartServiceFallback.class)
@GetMapping("/list")
public List<DepartVO> listHandle() {
return departService.listAllDeparts();
}
(3)测试
只启动消费者
2.4 Feign 式类级降级
(1)添加依赖
消费者端项目中添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
(2) 修改配置文件
feign:
sentinel: # 开启sentinel对feign的支持
enabled: true
(3) 定义降级类
在feign对外接口中添加DepartServiceFallback降级类
降级类实现feign接口,在方法中根据实际业务完成降级业务
// 降级类:实现了Feign接口
@Component
//@RequestMapping("/fallback/provider/depart") // 其开头必须是/fallback
public class DepartServiceFallback implements DepartService {
@Override
public boolean saveDepart(DepartVO depart) {
System.out.println("执行saveDepart()的服务降级处理方法");
return false;
}
@Override
public boolean removeDepartById(int id) {
System.out.println("执行removeDepartById()的服务降级处理方法");
return false;
}
@Override
public boolean modifyDepart(DepartVO depart) {
System.out.println("执行modifyDepart()的服务降级处理方法");
return false;
}
@Override
public DepartVO getDepartById(int id) {
System.out.println("执行getDepartById()的服务降级处理方法");
DepartVO depart = new DepartVO();
depart.setId(id);
depart.setName("degrade-feign");
return depart;
}
@Override
public List<DepartVO> listAllDeparts() {
System.out.println("执行listAllDeparts()的服务降级处理方法");
return null;
}
}
(4)修改feign接口
//在注解中指定降级类
@FeignClient( value="feign-nacos-provider-modules", path = "/provider/depart",
fallback = DepartServiceFallback.class)
public interface DepartService {
@PostMapping("/save")
boolean saveDepart(@RequestBody DepartVO depart);
@DeleteMapping("/del/{id}")
boolean removeDepartById(@PathVariable("id") int id);
@PutMapping("/update")
boolean modifyDepart(@RequestBody DepartVO depart);
@GetMapping("/get/{id}")
DepartVO getDepartById(@PathVariable("id") int id);
@GetMapping("/list")
List<DepartVO> listAllDeparts();
}
(5)测试
只启动消费者,模拟生产者异常。访问删除接口。结果发现执行了降级方法
3 Sentinel Dashboard
3.1 简介
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
Sentinel 控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,不提供安全可靠保障。若希望在生产环境使用请根据文档自行进行定制和改造。
3.2 下载
直接从官方下载打包好的 Sentinel Dashboard 启动运行
组件版本关系
Sentinel Dashboard 版本需要和Spring Cloud Alibaba Version对应,项目中Spring Cloud Alibaba Version为2.2.1.RELEASE,我们选用1.7.1以上版本。1.8版本和1.7版本界面变化有些大,选用新一点版本使用1.8.0下载
3.3 启动
启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888
-Dproject.name=sentinel-dashboard
-Dsentinel.dashboard.auth.username=sentinel
-Dsentinel.dashboard.auth.password=sentinel -jar sentinel-dashboard-1.8.0.jar
3.4 访问
http://localhost:8888/#/login
4 服务熔断
4.1熔断概念
(1) 服务雪崩
如果说服务流控是为了在高并发场景下不至于将系统压垮,那么服务熔断则是为了在外部环境不通畅场景下,不至于将系统拖垮,也就是为了防止服务雪崩的发生。
在复杂的系统中,经常会出现 A 依赖于 B,B 依赖于 C,C 依赖于 D,……这种依赖将会产生很长的调用链路,这种复杂的调用链路称为 1->N 的扇出。
如果在 A 的调用链路上某一个或几个被调用的子服务不可用或延迟较高,则会导致调用A 服务的请求被堵住(等待超时)。堵住的 A 请求会消耗占用系统的线程、IO 等资源,当对A 服务的请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,这种现象称为雪崩效应
。
(2) 服务熔断
Hystrix官网
为了防止服务雪崩的发生,在发现了对某些资源请求的响应缓慢或调用异常较多时,直接将对这些资源的请求掐断一段时间。而在这段时间内的请求将不再等待超时,而是直接返回事先设定好的降级结果。这些请求将不占用系统资源,从而避免了服务雪崩的发生。这就是服务熔断。
4.2 动态设置
对于服务熔断规则,可以通过 Sentinel Dashboard 进行动态设置。
4.2.1 修改处理器类
在消费者类处理器上配置降级名称,后续降级名称需要在sentinel dashboard中使用。这样客户端上配置的降级规则才能够知道给谁使用。
/**
* 定义了降级
*/
@SentinelResource(value = "slowRequstDegradeRule",fallback = "getHandleFallback")
@GetMapping("/get/{id}")
public DepartVO getHandle(@PathVariable("id") int id) {
return departService.getDepartById(id);
}
4.2.2 修改配置文件
spring:
application:
name: feign-nacos-consumer # 微服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos discovery地址
sentinel:
transport:
dashboard: localhost:8888 # Sentinel客户端地址
port: 8197 # 服务和客户端通信端口,在客户端上配置会生效到服务上需要发送请求
eager: true # 开启配置后立即生效
4.2.3 在sentinel 客户端设置降级规则
先启动消费者服务,在进入sentinel控制台。
4.2.4 修改提供者工程
设置睡眠时间,模拟接口请求慢。设置完成启动提供者
@GetMapping("/get/{id}")
public DepartVO getHandle(@PathVariable("id") int id) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(500);
DepartVO departVO = new DepartVO();
departVO.setId(1);
departVO.setName("liming");
return departVO;
}
4.2.5 测试
- 单次访问,可以发现能够正常取值。
http://localhost:8080/consumer/depart/get/1
- 在浏览器中快速按F5不断刷新接口,连续刷新8次
注意 我们使用的sentinel版本是1.7.1的,这个版本不支持修改比例阈值(默认为1),sentinel 客户端是1.8.0界面上虽然有比例阈值修改,但是不生效的。
3.紧接着再次刷新接口
还是返回的降级结果。设置了熔断时长为20s,在这20秒访问会直接走降级逻辑。
4.3 熔断策略
(1)响应时间 RT
慢调用比。该策略需要设置用于界定慢调用的响应时间阈值 RT(Response Time),
当请求的响应时间大于该值时,将该请求统计为慢调用。若要发生熔断,
在 1 秒内收到的请求数 量不能小于“最小请求数”,且慢调用占比不能低于“比例阈值”。
当触发熔断,则会在“熔断时长”内不再对请求进行处理,即熔断期间再来的请求,
将直接进行降级响应。
(2)异常比
异常比。该策略需要设置异常请求在统计时间窗口内所有请求中的占比,异常比率的阈
值范围是 [0.0, 1.0],代表 0% - 100%。当异常请求比例大于该值时则会触发熔断。
默认情况下,统计时间窗口大小为 1 秒,期间接收到的请求至少 5 个。
发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。
(3)异常数
异常数。该策略需要设置在统计时间窗口内所接收到的异常请求的数量。
当异常请求数量大于该值时则会触发熔断。默认情况下,统计时间窗口大小为 1 分钟,
注意,是 1 分钟。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,
将直接地降级响应。
4.4 代码设置
通过 Dashboard 平台进行熔断规则设置,粒度有些粗,有些属性只能通过代码来设置。而代码中的 API,在不同的 Sentinel 版本中,是有所不同的。
熔断规则直接定义在代码中,当应用启动时完成熔断规则的创建与初始化。这个熔断规则在 Dashboard 中也是可以查看到并且进行编辑的,编辑后以动态编辑的规则为准。
添加降级规则配置类
@Configuration
public class hystrixRuleConfig {
/**
* 初始化生效的降级规则
*/
@Bean
public void initRule() {
List<DegradeRule> rules = new ArrayList<>();
rules.add(slowRequestDegradeRule());
rules.add(errorRatioDegradeRule());
rules.add(errorCountDegradeRule());
DegradeRuleManager.loadRules(rules);
}
/**
* 慢调用
*/
@Bean
public DegradeRule slowRequestDegradeRule() {
//定义降级规则
DegradeRule degradeRule = new DegradeRule();
//指定降级规则名称
degradeRule.setResource("slowRequestDegradeRule");
//指定熔断策略为Rt(response time)
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
//指定熔断阈值为200ms(请求响应时长大于200ms,统计为慢调用)
degradeRule.setCount(200);
//指定熔断时长60s
degradeRule.setTimeWindow(60);
//指定在1秒的统计时间窗内至少要5个请求
degradeRule.setMinRequestAmount(5);
//指定若要启动熔断,则在1秒内至少要3次慢请求
degradeRule.setRtSlowRequestAmount(3);
return degradeRule;
}
/**
* 异常比
*/
@Bean
public DegradeRule errorRatioDegradeRule() {
DegradeRule degradeRule = new DegradeRule();
//指定降级规则名称
degradeRule.setResource("errorRatioDegradeRule");
//指定熔断策略为异常比
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
//指定熔断阈值为50%
degradeRule.setCount(0.5);
//指定熔断时长60s
degradeRule.setTimeWindow(60);
//指定在1秒的统计时间窗内至少要5个请求
degradeRule.setMinRequestAmount(5);
return degradeRule;
}
/**
* 异常数
*/
@Bean
public DegradeRule errorCountDegradeRule() {
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource("errorCountDegradeRule");
//指定熔断策略为异常数
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
//指定熔断阈值为3
degradeRule.setCount(3);
//指定熔断时长60s
degradeRule.setTimeWindow(60);
//指定在1秒的统计时间窗内至少要5个请求
degradeRule.setMinRequestAmount(5);
return degradeRule;
}
}
使用降级
启动消费者服务后我们观察一下控制台,发现三个降级规则都已成功注册。测试异常
连续发送三次请求
5 服务流控
5.1 流控概念
流控,即流量控制,也称为限流。Sentinel 实现流控的原理是监控应用流量的 QPS 或并 发线程数等指标,当达到指定的阈值时对再来的请求进行进行控制,以避免被瞬时的流量高 峰冲垮,从而保障应用的高可用性。
5.2 动态设置
流控规则直接通过 Sentinel Dashboard 定义,该规则可以随时修改而不需要重启应用该 规则的应用程序。所以这种流控是一种动态流控。
(1) 修改配置文件
server:
port: 8080
spring:
application:
name: feign-nacos-consumer # 微服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos discovery地址
sentinel:
transport:
dashboard: localhost:8888 # Sentinel客户端地址
port: 8197 # 服务和客户端通信端口,在客户端上配置会生效到服务上需要发送请求
eager: true # 开启配置后立即生效
(2)修改处理器 1-指定流控规则
/**
* 定义了降级,和限流
*/
@SentinelResource(value = "qpsFlowRule",
blockHandler = "getBlockHandler",
fallback = "getHandleFallback")
@GetMapping("/get/{id}")
public DepartVO getHandle(@PathVariable("id") int id) {
return departService.getDepartById(id);
}
public DepartVO getBlockHandler(int id, BlockException e) {
DepartVO departVO = new DepartVO();
departVO.setId(id);
departVO.setName("flow-control" + id + "-" + e.getMessage());
return departVO;
}
public DepartVO getHandleFallback(int id, Throwable e) {
DepartVO departVO = new DepartVO();
departVO.setId(id);
departVO.setName("degrade-method-" + id + "-" + e.getMessage());
return departVO;
}
(3) dashboard 设置流控规则
每秒访问量超过一次时,就会立刻进入限流函数。
(4)测试
http://localhost:8080/consumer/depart/get/1
5.3 代码设置
(1)添加限流配置类
@Configuration
public class flowRuleConfig {
@Bean
public void initFlowRule(){
List<FlowRule> flowRules=new ArrayList<>();
flowRules.add(qpsFlowRule());
FlowRuleManager.loadRules(flowRules);
}
@Bean
public FlowRule qpsFlowRule(){
FlowRule flowRule = new FlowRule();
flowRule.setResource("qpsFlowRule");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(1);
flowRule.setLimitApp("default");
return flowRule;
}
}
测试
项目重新启动,限流配置会注册到控制台