Sentinel实现熔断与限流
Sentinel介绍
官网
https://github.com/alibaba/Sentinel
中文
https://github.com/alibaba/Sentinel/wiki/介绍
是什么
一句话解释,之前的Hystrix
能干嘛
去哪下
https://github.com/alibaba/Sentinel/releases
怎么玩
- 服务使用中的各种问题
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
安装Sentinel控制台
sentinel组件由2部分组成
Sentinel 分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
- 后台
- 前台8080
安装步骤
下载
https://github.com/alibaba/Sentinel/releases
下载到本地sentinel-dashboard-1.8.2.jar
运行命令
前提
java8环境OK
8080端口不能被占用
命令
java -jar sentinel-dashboard-1.8.2.jar
访问sentinel管理界面
登录账号密码均为sentinel
初始化演示工程
启动Nacos8848成功
http://localhost:8848/nacos/#/login
案例
创建Module:cloudalibaba-sentinel-service8401
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2021</artifactId>
<groupId>com.aqrlmy.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<dependencies>
<dependency>
<groupId>com.aqrlmy</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默认8719,应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用HttpServer
management:
endpoints:
web:
exposure:
include: '*'
主启动
package com.aqrlmy.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401{
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
业务类FlowLimitController
package com.aqrlmy.springcloud.alibaba.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FlowLimitController{
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
启动Sentinel8080
java -jar sentinel-dashboard-1.8.2.jar
启动微服务8401
启动8401微服务后查看sentienl控制台
空空如也,啥都没有
Sentinel采用的懒加载说明
执行一次访问即可
效果
结论
sentinel8080正在监控微服务8401
流控规则
基本介绍
进一步解释说明
流控模式
直接(默认)
- 直接->快速失败
- 系统默认
- 测试QPS
- 配置及说明
- 表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
- 配置及说明
- 测试QPS
- 系统默认
- 快速点击访问: http://localhost:8401/testA
- 结果
- Blocked by Sentinel (flow limiting)
- 测试线程数
- 快速点击访问: http://localhost:8401/testA
- 结果
- 不会出现Blocked by Sentinel (flow limiting)(线程处理请求很快)
但是,在映射方法里添加sleep后,同样也会出现Blocked by Sentinel (flow limiting)默认提示信息。
- 思考???
- 直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
- 类似有一个fallback的兜底方法?
- 直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
关联
- 是什么?
- 当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阈值后,就限流自己
- B惹事,A挂了
- 当关联的资源达到阈值时,就限流自己
配置A
- 设置效果:
- 当关联资源/testB的QPS阀值超过1时,就限流/testA的REST访问地址,当关联资源到阀值后闲置配置的的资源名。
JMeter模拟并发密集访问testB
- 运行
- 大批量线程高并发访问B,导致A失效了
- 运行后发现testA挂了
- 点击访问http://localhost:8401/testA
- 结果
- Blocked by Sentinel (flow limiting)
链路
- 多个请求调用了同一个微服务
流控效果
直接->快速失败(默认的流控处理)
- 直接失败,抛出异常:Blocked by Sentinel (flow limiting)
- 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
预热
- 说明
- 公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
-
限流 冷启动
-
源码
- com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
-
Warmup配置
默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
-
案例:阈值为10 + 预热时长设置5秒。
-
系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高,恢复到10
- 多次点击http://localhost:8401/testB
- 刚开始不行,后续慢慢OK
- 应用场景
- 如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
排队等待
- 匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS,否则无效。
- 设置含义:/testB每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
- 官网
-
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
-
测试
- 增加打印语句
@GetMapping("/testB") public String testB() { log.info(Thread.currentThread().getName()+"\t ...testB"); return "------testB"; }
-
增加线程组:直接10个线程并发,排队被依次处理
熔断规则
官网
https://github.com/alibaba/Sentinel/wiki/熔断降级
基本介绍
- 整体介绍
熔断策略实战
慢调用比例
- 是什么
- 测试
- 代码
@GetMapping("/testA")
public String testA() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "------testA";
}
- 配置
- 访问测试: http://localhost:8401/testA
- 5秒内打进10个请求,由于每次请求都大于RT,并且比例阈值100%,所以,熔断器打开。
异常比例
- 是什么
- 测试
- 代码
@GetMapping("/testB")
public String testB() {
int age = 10/0;
return "------testB";
}
- 配置
- 访问测试: http://localhost:8401/testB
- 5秒内打进10个请求,由于每次请求都抛异常,异常比例阈值100%超过50%,所以,熔断器打开,10s后半开。如果再次访问有异常,则继续熔断。
异常数
- 是什么
- 测试
- 代码
@GetMapping("/testB")
public String testB(){
int age = 10/0;
return "------testB 测试异常数";
}
- 配置
- 访问测试: http://localhost:8401/testB
- 5秒内打进10个请求,由于每次请求都抛异常,异常数超过5个,所以,熔断器打开,10s后半开。如果再次访问有异常,则继续熔断。
热点规则
基本介绍
- 是什么
官网
https://github.com/alibaba/Sentinel/wiki/热点参数限流
承上启下复习
- 兜底方法
- 分为系统默认和客户自定义,两种
- 之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel(flow limiting)
- 我们能不能自定义?类似hystrix,某个方法出现问题了,就找对应的兜底降级方法?
- 结论
- 从@HystrixCommand到@SentinelResource
- 分为系统默认和客户自定义,两种
代码
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
com.alibaba.csp.sentinel.slots.block.BlockException
配置
- 配置
- 默认
- @SentinelResource(value = “testHotKey”)
- 异常打到了前台用户界面,不友好
-
自定义兜底方法
- @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”) //value值与资源名一致即可
- 方法testHostKey里面第一个参数只要QPS超过每秒1次,马上降级处理
-
测试
-
error (1秒1下可以,但是,超过则降级,和p1参数有关)
-
-
error(1秒1下可以,但是,超过则降级,和p1参数有关)
-
right(狂点不会触发降级,与p2参数无关)
参数例外项
- 上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
- 特殊情况
- 普通
- 超过1秒钟一个后,达到阈值1后马上被限流
- 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
- 特例
- 假如当p1的值等于5时,它的阈值可以达到200
- 普通
- 配置
- 添加按钮不能忘
- 特殊情况
-
测试
-
当p1等于5的时候,阈值变为200
-
当p1不等于5的时候,阈值就是平常的1
-
前提条件
- 热点参数的注意点,参数必须是基本类型或者String
其他
- 手动添加一个异常看看…
- @SentinelResource
- 处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理
- RuntimeException
- Int age = 10/0;这个是java运行时报出的运行时异常RuntimeException,@SentinelResource不管
- 总结:
- @SentinelResource主管配置出错,运行出错该走异常走异常
- @SentinelResource
系统规则
是什么
https://github.com/alibaba/Sentinel/wiki/系统自适应限流
各项配置参数说明
配置全局QPS
@SentinelResource
官网
https://github.com/alibaba/Sentinel/wiki/注解支持
流控规则+自定义处理
- 方法
//注意: 此处的value不能是/testC,否则请求/testC时会直接调用自定义的兜底方法
@SentinelResource(value = "testC", blockHandler = "backup_testC")
@GetMapping("/testC")
public String testC() {
return "请求执行成功......testC";
}
//自定义兜底方法
public String backup_testC(BlockException e) {
e.printStackTrace();
return "自定义兜底数据......testC";
}
-
配置流控规则
上面兜底方法面临的问题
- 如果使用系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一起,不直观。
- 每个业务方法都增加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
集中自定义处理
- 创建CustomerBlockHandler类用于自定义限流处理逻辑
- 自定义限流处理类
- 方法必须是public static修饰的。
- 自定义限流处理类
package com.aqrlmy.springcloud.alibaba.myhandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.aqrlmy.springcloud.entities.CommonResult;
public class CustomerBlockHandler {
public static String handleException1(BlockException exception){
return "自定义限流处理信息.... CustomerBlockHandler --- 1";
}
public static String handleException2(BlockException exception){
return "自定义限流处理信息.... CustomerBlockHandler --- 2";
}
}
- FlowLimitController
@SentinelResource(value = "testD", blockHandler = "handleException1",
blockHandlerClass = CustomerBlockHandler.class)
@GetMapping("/testD")
public String testD() {
return "请求执行成功......testD";
}
-
启动微服务后先调用一次
-
Sentinel控制台配置流控规则
-
测试后我们自定义的出来了
更多注解属性说明
/**
* @SentinelResource 与 Hystrix 组件中的@HystrixCommand注解作用是类似的。
* value = "byResourceName" 用于设置资源名称,名称不能与方法的映射路径完全一致,否则正常的请求会直接调用降级方法
* blockHandler 用于引用降级方法。
* blockHandlerClass 用于引用降级方法的处理器类。注意:降级方法必须是static的。否则,无法解析
* blockHandler + blockHandlerClass 只处理配置违规,进行降级处理。代码出现异常,不执行的。
*
* blockHandler + fallback 同时存在,配置违规,代码也有异常,这时,走blockHandler配置文件降级处理
*
* exceptionsToIgnore 设置特定异常不需要降级处理,将异常原样抛出。
*/
@RequestMapping("/fallback/{id}")
@SentinelResource(value = "byFallbackName",
blockHandler = "handleException2", blockHandlerClass = CustomerBlockHandler.class,
fallback = "handleException3", fallbackClass = CustomerBlockHandler.class,
exceptionsToIgnore = IllegalArgumentException.class
)
public String fallback(@PathVariable("id") Long id) {
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
}
if (id == -1) {
CommonResult<Payment> result = new CommonResult<>(444, "数据不存在", null);
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}
return "请求成功,获取到数据" + id;
}
熔断框架比较
在生产环境中使用 Sentinel · alibaba/Sentinel Wiki (github.com)
规则持久化
是什么
一旦我们停止应用,该应用的Sentinel规则将消失,生产环境需要将配置规则进行持久化
怎么做
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效
步骤
修改:cloudalibaba-sentinel-service8401
POM
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
添加Nacos业务规则配置
- 内容解析
[
{
"resource": "/testA",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
启动8401并访问接口
访问8401的任意接口,如:http://localhost:8401/testA
刷新sentinel发现业务规则有了
快速访问测试接口
流控规则生效,响应默认的兜底数据
停止8401再看sentinel
因为应用停止,所以监测不到数据
重新启动8401并访问接口
访问8401的接口:http://localhost:8401/testA
再看sentinel,配置出现了,持久化验证通过
了解
若需要配置其它规则,参照对应类的属性进行json的配置
流控规则:com.alibaba.csp.sentinel.slots.block.flow.FlowRule
熔断/降级规则:com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule
热点规则:com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule
系统规则:com.alibaba.csp.sentinel.slots.system.SystemRule
授权规则:com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule
之前的配置可以让我们启动项目就加载nacos配置好的规则,但是在Sentinel 控制台上修改后,nacos中的配置文件不会修改。
如果要做到同步到nacos中,需要对源码进行扩展。
参考文档:
Sentinel 基于Nacos规则持久化-推模式
[(企业案例)使用Nacos持久化规则,改造sentinel-dashboard](