作者:🍧dabing(王甜甜)
视频:尚硅谷 2020 周阳 SpringCloud(H 版 & alibaba)
官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#features
github 上代码地址:https://github.com/dabing85/springcloud2020
gitee 上代码地址:https://gitee.com/hedabing/springcloud2020
笔记来自视频,只不过再加些自己练习过程的笔记。
# 十八、SpringCloud Alibaba Sentinel 实现熔断与限流
这个跟 Hystrix 断路器差不多,不过这个多了一些规则,Hystrix 是处理异常比例达到一定阈值时进行服务降级,而这个 Sentinel 会有更多规则,异常比较、慢调用比例、异常数的规则,还有一些限流的规则,还有自己的控制台可视化比较好操作等等。第 9 点有对比列表。
# 1 - Sentinel 介绍
# 1.1. 官网
https://github.com/alibaba/Sentinel
中文
https://github.com/alibaba/Sentinel/wiki/ 介绍
# 1.2. 是什么
一句话解释,之前我们讲解过的 Hystrix
# 1.3. 能干嘛
# 1.4. 去哪下
https://github.com/alibaba/Sentinel/releases
# 1.5. 怎么玩
-
服务使用中的各种问题
-
服务雪崩
-
服务降级
-
服务熔断
-
服务限流
-
# 2 - 安装 Sentinel 控制台
# 2.1. sentinel 组件由 2 部分组成
Sentinel 分为两个部分:
核心库(Java 客户端)不依赖任何框架 / 库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
-
后台
-
前台 8080
# 2.2. 安装步骤
# 1. 下载
https://github.com/alibaba/Sentinel/releases
下载到本地 sentinel-dashboard-1.8.2.jar,它是一个 jar 直接用 java -jar 命令启动即可。
# 2. 运行命令
前提
java8 环境 OK
8080 端口不能被占用
命令
java -jar sentinel-dashboard-1.8.2.jar |
# 3. 访问 sentinel 管理界面
http://localhost:8080
登录账号密码均为 sentinel,刚进来是啥都没有:
# 3 - 初始化演示工程
# 3.1. 启动 Nacos8848 成功
http://localhost:8848/nacos/#/login
# 3.2. 案例
# 1. 创建 Module:cloudalibaba-sentinel-service8401
# 2. 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.dabing.springcloud</groupId> | |
<version>1.0-SNAPSHOT</version> | |
</parent> | |
<modelVersion>4.0.0</modelVersion> | |
<artifactId>cloudalibaba-sentinel-service8401</artifactId> | |
<dependencies> | |
<dependency> | |
<groupId>com.dabing</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> |
# 3. 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: '*' |
# 4. 主启动
package com.dabing.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); | |
} | |
} |
# 5. 业务类 FlowLimitController
package com.dabing.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"; | |
} | |
} |
# 3.3. 启动 Sentinel8080
java -jar sentinel-dashboard-1.8.2.jar |
# 3.4. 启动微服务 8401
# 3.5. 启动 8401 微服务后查看 sentienl 控制台
空空如也,啥都没有
Sentinel 采用的懒加载说明
执行一次访问即可
http://localhost:8401/testA
http://localhost:8401/testB
效果
结论
sentinel8080 正在监控微服务 8401
接下来可以测试 sentinel 的流控、熔断等功能。
# 4 - 流控规则
# 4.1. 基本介绍
进一步解释说明
# 4.2. 流控模式
# 1. 直接(默认)
直接 -> 快速失败
- 系统默认
测试 QPS
-
配置及说明
表示 1 秒钟内查询 1 次就是 OK, 若超过次数 1,就直接 - 快速失败,报默认错误
-
快速点击访问: 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 的兜底方法?
# 2. 关联
是什么?
当关联的资源达到阈值时,就限流自己
当与 A 关联的资源 B 达到阈值后,就限流自己
B 惹事,A 挂了
# 3. 配置 A
设置效果:
当关联资源 /testB 的 QPS 阀值超过 1 时,就限流 /testA 的 REST 访问地址,当关联资源到阀值后闲置配置的的资源名。
# 4. postman 模拟并发密集访问 testB
访问 testB 成功
postman 里新建多线程集合组,将请求保存到集合组
运行线程集合组
设置并发访问参数
Run
- 大批量线程高并发访问 B,导致 A 失效了
运行后发现 testA 挂了
-
点击访问 http://localhost:8401/testA
-
结果
- Blocked by Sentinel (flow limiting)
# 5. 链路
多个请求调用了同一个微服务
家庭作业试试
# 4.3. 流控效果
# 1. 直接 -> 快速失败(默认的流控处理)
-
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
-
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
# 2. 预热
-
说明
- 公式:阈值除以 coldFactor(默认值为 3),经过预热时长后才会达到阈值
- 限流 冷启动
https://github.com/alibaba/Sentinel/wiki/ 限流 --- 冷启动
- 源码
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
§ 应用场景
・如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
# 3. 排队等待
匀速排队,让请求以均匀的速度通过,阈值类型必须设置成 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 个线程并发,排队被依次处理
# 5 - 降级规则
# 5.1. 官网
https://github.com/alibaba/Sentinel/wiki/ 熔断降级
# 5.2. 基本介绍
整体介绍
# 5.3. 降级策略实战
# 1. 慢调用比例
是什么
测试:
代码
@GetMapping("/testA") | |
public String testA() { | |
try { | |
Thread.sleep(300); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
return "------testA"; | |
} |
配置:
§ 访问测试: http://localhost:8401/testA
§ 5 秒内打进 10 个请求,由于每次请求都大于 RT,并且比例阈值 100%,所以,熔断器打开。
# 2. 异常比例
是什么
测试:
代码
@GetMapping("/testB") | |
public String testB() { | |
int age = 10/0; | |
return "------testB"; | |
} |
§ 配置
§ 访问测试: http://localhost:8401/testB
§ 5 秒内打进 10 个请求,由于每次请求都抛异常,异常比例阈值 100% 超过 50%,所以,熔断器打开,10s 后半开。如果再次访问有异常,则继续熔断。
# 3. 异常数
是什么
测试:
代码
@GetMapping("/testB") | |
public String testB(){ | |
int age = 10/0; | |
return "------testB 测试异常数"; | |
} |
配置
§ 访问测试: http://localhost:8401/testB
§ 5 秒内打进 10 个请求,由于每次请求都抛异常,异常数超过 5 个,所以,熔断器打开,10s 后半开。如果再次访问有异常,则继续熔断。
# 6 - 热点 key 限流
# 6.1. 基本介绍
是什么
# 6.2. 官网
https://github.com/alibaba/Sentinel/wiki/ 热点参数限流
# 6.3. 承上启下复习
兜底方法
分为系统默认和客户自定义,两种
之前的 case,限流出问题后,都是用 sentinel 系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定义?类似 hystrix, 某个方法出现问题了,就找对应的兜底降级方法?
结论
从 @HystrixCommand 到 @SentinelResource
# 6.4. 代码
@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
# 6.5. 配置
- 配置
- 默认
-
@SentinelResource(value = “testHotKey”)
-
异常打到了前台用户界面,不友好
- 自定义
-
@SentinelResource (value = “testHotKey”,blockHandler = “deal_testHotKey”) //value 值与资源名一致即可
-
方法 testHostKey 里面第一个参数只要 QPS 超过每秒 1 次,马上降级处理
- 测试
- error (1 秒 1 下可以,但是,超过则降级,和 p1 参数有关)
http://localhost:8401/testHotKey?p1=abc
- error(1 秒 1 下可以,但是,超过则降级,和 p1 参数有关)
http://localhost:8401/testHotKey?p1=abc&p2=33
- right(狂点不会触发降级,与 p2 参数无关)
http://localhost:8401/testHotKey?p2=abc
# 6.6. 参数例外项
上述案例演示了第一个参数 p1, 当 QPS 超过 1 秒 1 次点击后马上被限流
特殊情况
普通
超过 1 秒钟一个后,达到阈值 1 后马上被限流
我们期望 p1 参数当它是某个特殊值时,它的限流值和平时不一样
特例
假如当 p1 的值等于 5 时,它的阈值可以达到 200
配置
添加按钮不能忘
测试
http://localhost:8401/testHotKey?p1=5 对
http://localhost:8401/testHotKey?p1=3 错
-
当 p1 等于 5 的时候,阈值变为 200
-
当 p1 不等于 5 的时候,阈值就是平常的 1
前提条件
- 热点参数的注意点,参数必须是基本类型或者 String
# 6.7. 其他
手贱添加异常看看…
@SentinelResource
处理的是 Sentinel 控制台配置的违规情况,有 blockHandler 方法配置的兜底处理
RuntimeException
Int age = 10/0; 这个是 java 运行时报出的运行时异常 RuntimeException,@SentinelResource 不管
总结:
@SentinelResource 主管配置出错,运行出错该走异常走异常,不像 Hystrix 会走兜底的方法,Sentinel 配置的熔断规则和未配置的异常是分开的。
# 7 - 系统规则
# 7.1. 是什么
https://github.com/alibaba/Sentinel/wiki/ 系统自适应限流
# 7.2. 各项配置参数说明
# 7.3. 配置全局 QPS
# 8 - @SentinelResource
# 8.1. 按资源名称限流 + 后续处理
通过访问的 URL 来限流,会返回自定义的限流处理信息
启动 Nacos 成功
启动 Sentinel 成功
Module
-
cloudalibaba-sentinel-service8401
-
POM
-
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: '*' |
- 业务类 RateLimitController
package com.dabing.springcloud.alibaba.controller; | |
import com.alibaba.csp.sentinel.annotation.SentinelResource; | |
import com.alibaba.csp.sentinel.slots.block.BlockException; | |
import com.dabing.springcloud.alibaba.entities.CommonResult; | |
import com.dabing.springcloud.alibaba.entities.Payment; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.RestController; | |
@RestController | |
public class RateLimitController{ | |
@GetMapping("/byResource") | |
@SentinelResource(value = "byResource",blockHandler = "handleException") | |
public CommonResult byResource(){ | |
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001")); | |
} | |
public CommonResult handleException(BlockException exception){ | |
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用"); | |
} | |
} |
- 主启动类
package com.dabing.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); | |
} | |
} |
配置流控规则
配置步骤:
图形配置和代码关系:
表示 1 秒钟内查询次数大于 1,就跑到我们自定义的处理,限流
测试:
1 秒钟点击 1 下,OK
超过上述问题,疯狂点击,返回了自己定义的限流处理信息,限流发生
额外问题:
此时关闭微服务 8401 看看
Sentinel 控制台,流控规则消失了?????
临时 / 持久?----- 临时,所以后面需要配置进 nacos 里持久化
# 8.2. 按照 Url 地址限流 + 后续处理
通过访问的 URL 来限流,会返回 Sentinel 自带默认的限流处理信息
业务类 RateLimitController
@GetMapping("/rateLimit/byUrl") | |
@SentinelResource(value = "byUrl") | |
public CommonResult byUrl(){ | |
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002")); | |
} |
o 访问一次
o Sentinel 控制台配置
测试:
疯狂点击 http://localhost:8401/rateLimit/byUrl
结果:
会返回 Sentinel 自带的限流处理结果
# 8.3. 上面兜底方法面临的问题
系统默认的,没有体现我们自己的业务要求。
依照现有条件,我们自定义的处理方法又和业务代码耦合在一起,不直观。
每个业务方法都增加一个兜底的,那代码膨胀加剧。
全局统一的处理方法没有体现。
# 8.4. 客户自定义限流处理逻辑
创建 customerBlockHandler 类用于自定义限流处理逻辑
自定义限流处理类
方法必须是 public static 修饰的。
package com.dabing.springcloud.alibaba.myhandler; | |
import com.alibaba.csp.sentinel.slots.block.BlockException; | |
import com.dabing.springcloud.entities.CommonResult; | |
public class CustomerBlockHandler { | |
public static CommonResult handleException(BlockException exception){ | |
return new CommonResult(2020,"自定义限流处理信息.... CustomerBlockHandler --- 1"); | |
} | |
public static CommonResult handleException2(BlockException exception){ | |
return new CommonResult(2020,"自定义限流处理信息.... CustomerBlockHandler --- 2"); | |
} | |
} |
RateLimitController
@GetMapping("/rateLimit/customerBlockHandler") | |
@SentinelResource(value = "customerBlockHandler", | |
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2") | |
public CommonResult customerBlockHandler(){ | |
return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003")); | |
} |
o 启动微服务后先调用一次
http://localhost:8401/rateLimit/customerBlockHandler
o Sentinel 控制台配置
o 测试后我们自定义的出来了
o 进一步说明
# 8.5. 更多注解属性说明
https://github.com/alibaba/Sentinel/wiki/ 注解支持
/** | |
* @SentinelResource 与 Hystrix 组件中的 @HystrixCommand 注解作用类似的。 | |
* value = "byResourceName" 用于设置资源名称,只有根据资源名称设置的规则,才能执行 blockHandler 所引用降级方法。 | |
* 如果按照映射路径进行规则配置,返回默认降级消息:Blocked by Sentinel (flow limiting) | |
* blockHandler 用于引用降级方法。 | |
* blockHandlerClass 用于引用降级方法的处理器类。注意:降级方法必须是 static 的。否则,无法解析 | |
* blockHandler + blockHandlerClass 只处理配置违规,进行降级处理。代码出现异常,不执行的。 | |
* | |
* blockHandler + fallback 同时存在,配置违规,代码也有异常,这时,走 blockHandler 配置文件降级处理 | |
* | |
* exceptionsToIgnore 设置特定异常不需要降级处理。 | |
*/ | |
@RequestMapping("/fallback/{id}") | |
@SentinelResource(value = "byFallbackName",blockHandler = "handleException3", | |
blockHandlerClass = RateLimitControllerHandler.class, | |
fallback = "handleException2",fallbackClass = RateLimitControllerHandler.class, | |
exceptionsToIgnore=IllegalArgumentException.class | |
) | |
public CommonResult<Payment> 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没有对应记录,空指针异常"); | |
} | |
CommonResult<Payment> result = new CommonResult<>(200,"数据已经获取",new Payment(id,"test"+1)); | |
return result; | |
} |
# 9 - 熔断框架比较
# 10 - 规则持久化
# 10.1. 是什么
一旦我们重启应用,Sentinel 规则将消失,生产环境需要将配置规则进行持久化
# 10.2. 怎么玩
将限流配置规则持久化进 Nacos 保存,只要刷新 8401 某个 rest 地址,sentinel 控制台的流控规则就能看到,只要 Nacos 里面的配置不删除,针对 8401 上 Sentinel 上的流控规则持续有效
# 10.3. 步骤
# 1. 修改:cloudalibaba-sentinel-service8401
# 2. POM
<dependency> | |
<groupId>com.alibaba.csp</groupId> | |
<artifactId>sentinel-datasource-nacos</artifactId> | |
</dependency> |
# 3. 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: '*' |
# 4. 添加 Nacos 业务规则配置
・内容解析
[ | |
{ | |
"resource": "/testA", | |
"limitApp": "default", | |
"grade": 1, | |
"count": 1, | |
"strategy": 0, | |
"controlBehavior": 0, | |
"clusterMode": false | |
} | |
] |
# 5. 启动 8401 后刷新 sentinel 发现业务规则有了
# 6. 快速访问测试接口
http://localhost:8401/testA
默认
# 7. 停止 8401 再看 sentinel
# 8. 重新启动 8401 再看 sentinel
扎一看还是没有,稍等一会儿
多次调用
http://localhost:8401/testA
重新配置出现了,持久化验证通过