Spring Cloud Alibaba学习笔记
- Sentinel流量防卫兵
-
- Sentinel 简介
-
- 官方介绍
- 总结
- 服务降级
-
- 降级实现方式分类
- Sentinel 式方法级降级
- Sentinel 式类级降级
- Feign 式类级降级
- Sentinel Dashboard
-
- 简介
- 下载
- 启动
- 访问
- 服务熔断
-
- 熔断概念
- 动态设置
- 熔断策略
-
- 慢调用比例
- 异常比例
- 异常数
- 代码设置
- 服务流控
-
- 流控概念
- 动态设置
-
- 设置方法一
- 设置方法二
- 代码设置
-
- 使用注解
- 不使用注解
- 阈值类型分类
-
- QPS流控超值处理方案
- 并发线程数流控方案
- 流控模式分类
-
- 直接
- 关联
- 链路
- 流控效果分类
-
- 快速失败
- Warm Up
- 排队等待
- 限流算法(回顾)
- 流控方案代码设置
-
- 线程隔离流控
- QPS 默认流控
- 关联流控模式
- 链路流控模式
- WarmUp 流控
- 排队等待流控
- 来源流控
-
- 概念
- 定义原始请求解析器
- 流控规则中的来源指定
- 动态设置黑白名单
- 代码设置黑白名单
Sentinel流量防卫兵
Sentinel 简介
官方介绍
- wiki地址:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
总结
- Sentinel 是分布式系统的防御系统。以流量为切入点,通过动态设置的流量控制、服务熔断等手段达到保护系统的目的,通过服务降级增强服务被拒用户的体验。
服务降级
- 服务降级是一种增强用户体验的方式。当用户的请求由于各种原因被拒后,系统返回一个事先设定好的、用户可以接受的,但又令用户并不满意的结果。这种请求处理方式称为服务降级。
降级实现方式分类
对于 Sentinel,服务降级的实现方式根据消费者类型的不同,其支持两种方式:
- Sentinel 式降级:通过 Sentinel 自身的 API 实现的降级方式,适用于任意消费者类型。而根据降级方法应用范围、定义位置及可维护性的不同,又可分为两种:
- 方法级降级:降级方法与原方法定义在同一个类中,其仅是本类中的原方法可以使用。
- 类级降级:降级方法定义在专门的一个类中,其是一个可以被共享的降级类。该类中的所有方法均为降级方法,所以便于维护与管理。
- Feign 式降级:通过 OpenFeign 的 API 实现的降级方式,仅适用于 Feign 客户端的消费者类型,其只有类级降级方式。
Sentinel 式方法级降级
- 创建工程:复制 02-consumer-nacos-8080 工程,重命名为 06-consumer-sentinel-degrade-method-8080。
- 引入依赖:
<!-- sentinel 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 修改处理器:
- 测试地址:http://localhost:8080/consumer/depart/get/1
Sentinel 式类级降级
- 创建工程:复制 06-consumer-sentinel-degrade-method-8080,重命名为 06-consumer-sentinel-degrade-class-8080。
- 定义降级类:
/**
* 自定义降级类
*/
public class DepartServiceFallback {
public static Depart findByIdFallback(int id, Throwable e) {
return new Depart()
.setId(id)
.setName("degrade-class-" + id + "-" + e.getMessage());
}
public static List<Depart> listFallback() {
return Collections.singletonList(new Depart().setName("no any depart"));
}
}
- 修改处理器:
- 测试地址:http://localhost:8080/consumer/depart/get/1、http://localhost:8080/consumer/depart/list
Feign 式类级降级
- 创建工程:复制 04-consumer-feign-8080 工程,并重命名为 06-consumer-sentinel-degrade-feign-8080。这个消费者是通过 Feign 接口进行消费的。
- 添加依赖:
<!-- sentinel 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 修改配置文件:
- 定义降级类:
/**
* 自定义降级类
*/
@Slf4j
@Component
@RequestMapping("/fallback/consumer/depart") // 必须以 /fallback 开头
public class DepartServiceFallback implements DepartService { // 实现Feign接口
@Override
public boolean save(Depart depart) {
log.info("执行save()的服务降级处理方法");
return false;
}
@Override
public boolean deleteById(int id) {
log.info("执行deleteById()的服务降级处理方法");
return false;
}
@Override
public boolean update(Depart depart) {
log.info("执行update()的服务降级处理方法");
return false;
}
@Override
public Depart findById(int id) {
log.info("执行findById()的服务降级处理方法");
return new Depart()
.setId(id)
.setName("degrade-feign");
}
@Override
public List<Depart> list() {
log.info("执行list()的服务降级处理方法");
return Collections.singletonList(new Depart().setName("no any depart"));
}
}
- 修改 Feign 接口:
Sentinel Dashboard
简介
- Sentinel Dashboard 是 Sentinel 的一个轻量级开源 GUI 控制台,可以提供对 Sentinel 主机(Sentinel 应用)的发现及健康管理、动态配置服务流控、熔断、路由规则的配置与管理。
- 官方文档github地址:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
下载
- 直接从官方下载打包好的 Sentinel Dashboard 启动运行,下载地址:https://github.com/alibaba/Sentinel/releases
wget https://github.com/alibaba/Sentinel/releases/download/v1.8.0/sentinel-dashboard-1.8.0.jar
启动
- 启动 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=123456 -jar sentinel-dashboard-1.8.0.jar
访问
- 地址:http://192.168.254.130:8888
服务熔断
熔断概念
- 参看之前的博客:JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (G 版)(3)
动态设置
- 对于服务熔断规则,一般是提前设置好的,可以通过 Sentinel Dashboard 进行动态设置。
- 创建工程:复制 06-consumer-sentinel-degrade-method-8080 工程,重命名为 06-consumer-sentinel-circuitbreaking-8080。
- 修改处理器类:
- 修改配置文件:
- 修改提供者工程:使用之前的 04-provider-nacos-8081 工程
- 启动提供者工程、消费者工程之后,此时访问 http://localhost:8080/consumer/depart/get/1 是没有问题的。
- 打开 Sentinel dashboard 设置规则:
- 测试地址:http://localhost:8080/consumer/depart/get/1,在浏览器不断刷新,测下熔断降级效果。
熔断策略
慢调用比例
- 该策略需要设置用于界定慢调用的响应时间阈值 RT(Response Time),当请求的响应时间大于该值时,将该请求统计为慢调用。若要发生熔断,在 1 秒内收到的请求数量不能小于“最小请求数”,且慢调用占比不能低于“比例阈值”。当触发熔断,则会在“熔断时长”内不再对请求进行处理,即熔断期间再来的请求,将直接进行降级响应。
异常比例
- 该策略需要设置异常请求在统计时间窗口内所有请求中的占比,异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。当异常请求比例大于该值时则会触发熔断。默认情况下,统计时间窗口大小为 1 秒,期间接收到的请求至少 5 个。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。
异常数
- 该策略需要设置在统计时间窗口内所接收到的异常请求的数量。当异常请求数量大于该值时则会触发熔断。默认情况下,统计时间窗口大小为 1 分钟,注意,是 1 分钟。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。
代码设置
- 通过 Dashboard 平台进行熔断规则设置,粒度有些粗,有些属性只能通过代码来设置。而代码中的 API,在不同的 Sentinel 版本中,是有所不同的。
- 熔断规则直接定义在代码中,当应用启动时完成熔断规则的创建与初始化。这个熔断规则在 Dashboard 中也是可以查看到并且进行编辑的,编辑后以动态编辑的规则为准。
- 下面直接在 06-consumer-sentinel-circuitbreaking-8080 工程中修改演示。在启动类中添加对熔断规则的定义与设置代码。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Consumer068080 {
public static void main(String[] args) {
SpringApplication.run(Consumer068080.class, args);
// 初始化规则
initRule();
}
private static void initRule() {
List<DegradeRule> rules = new ArrayList<>();
rules.add(slowRequestDegradeRule());
DegradeRuleManager.loadRules(rules);
}
private static DegradeRule slowRequestDegradeRule() {
// 创建一个降级规则实例
DegradeRule rule = new DegradeRule();
// 指定该规则要应用的资源名称
rule.setResource("findById");
// 指定熔断规则为:慢调用比例
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
// 设置阈值:RT的时间,单位毫秒。若一个请求获取到响应时间超出该值,则会将该请求统计为"慢调用"
rule.setCount(200);
// 设置熔断窗口大小,单位秒
rule.setTimeWindow(30);
// 设置最小请求数量
rule.setMinRequestAmount(5);
// 设置发生慢调用的比例
rule.setSlowRatioThreshold(0.5);
return rule;
}
private static DegradeRule exceptionRatioDegradeRule() {
DegradeRule rule = new DegradeRule();
rule.setResource("findById");
// 指定熔断规则为:异常比例
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
// 设置阈值:发生熔断的异常请求比例
rule.setCount(0.5);
rule.setTimeWindow(60);
rule.setMinRequestAmount(5);
return rule;
}
private static DegradeRule exceptionCountDegradeRule() {
DegradeRule rule = new DegradeRule();
rule.setResource("findById");
// 指定熔断规则为:异常数
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 设置阈值:发生熔断的异常请求比例
rule.setCount(5);
rule.setTimeWindow(60);
rule.setMinRequestAmount(5);
return rule;
}
}
服务流控
流控概念
- 流控,即流量控制,也称为限流。Sentinel 实现流控的原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对再来的请求进行进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
动态设置
- 流控规则直接通过 Sentinel Dashboard 定义,该规则可以随时修改而不需要重启应用该规则的应用程序。所以这种流控是一种动态流控。
- 创建工程:复制 06-consumer-sentinel-degrade-method-8080 工程,并重命名为 06-consumer-sentinel-flowcontrol-8080。
- 修改配置文件,增加 Sentinel dashboard 的配置。
设置方法一
- 修改处理器:指定流控规则
- dashboard 设置流控规则:
- 测试地址:http://localhost:8080/consumer/depart/get/1,在浏览器不断刷新,测下流控效果。
设置方法二
- 修改处理器:添加流控处理方法
- 测试地址:http://localhost:8080/consumer/depart/get/1,在浏览器不断刷新,测下流控效果。
代码设置
使用注解
- 流控规则直接定义在代码中,当应用启动时完成流控规则的创建与初始化。这个流控规则在 Dashboard 中也是可以查看到并且进行编辑的,编辑后以动态编辑的规则为准。
- 直接在 06-consumer-sentinel-flowcontrol-8080 工程启动类中进行修改演示。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Consumer068080 {
public static void main(String[] args) {
SpringApplication.run(Consumer068080.class, args);
initFlowRule();
}
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<>();
rules.add(qpsFlowRule());
FlowRuleManager.loadRules(rules);
}
private static FlowRule qpsFlowRule() {
FlowRule rule = new FlowRule();
rule.setRefResource("qpsFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
// 值为default,表示对请求来源不做限定
rule.setLimitApp("default");
return rule;
}
}
不使用注解
- 流控规则在代码中的应用,一般是通过@SentinelResource 指定的。但也可以通过代码指定。只不过这种方式不提倡使用。
- 直接在 06-consumer-sentinel-flowcontrol-8080 工程启动类中进行修改演示。
阈值类型分类
- 流量控制主要有两种阈值统计类型,一种是 QPS,一种是统计并发线程数。
QPS流控超值处理方案
- QPS:Queue Per Second,就是通常所说的每秒的访问量。该流控方案通过对每秒访问量的控制来达到保护。当指定资源的每秒访问量达到了设置的阈值时,可以立即拒绝再新进入的请求。
并发线程数流控方案
- 并发线程数流控方案通常是对消费者端的配置。是为了避免由于慢调用而将消费者端线程耗尽情况的发生,业内会使用隔离方案。一般来说可以分为两种:
- 线程池隔离:系统为不同的提供者资源设置不同的线程池来隔离业务自身之间的资源争抢。该方案隔离性较好,但需要创建的线程池及线程数量太多,系统消耗较大。当请求线程到达后,会从线程池中获取到一个新的执行线程去完成提供者的调用。由请求线程到执行线程的上下文切换时间开销较大,特别是对低延时调用有比较大的影响。
- 信号量隔离:系统为不同的提供者资源设置不同的计数器。每增加一个该资源的调用请求,计数器就变化一次。当达到该计数器阈值时,再来的请求将被限流。该方式的执行线程与请求线程是同一个线程,不存在线程上下文切换的问题。更不存在很多的线程池创建与线程创建问题。也正因为请求线程与执行线程没有分离,所以,其对于提供者的调用无法实现异步,执行效率降低,且对于依赖资源的执行超时控制不方便。
- Hystrix 官方原理图:
- Sentinel 隔离方案:Sentinel 并发线程数控制也属于隔离方案,但不同于以上两种隔离方式,是对以上两种方案的综合与改进,或者说更像是线程池隔离。其也将请求线程与执行线程进行了分离,但不负责创建和管理线程池,而仅仅是简单统计该资源的请求占用的线程数量超出了阈值,则可以立即拒绝再新进入的请求。
流控模式分类
直接
- 当对“资源名”指定资源的请求达到了设置阈值时,再新进入的请求将被直接执行指定的“流控效果”。
关联
- 该模式比较特殊:当对别人的访问达到自己设置的阈值时,将开启对自己的限流。确切地说是,当对“关联资源”的访问达到了“单机阈值”指定的阈值时,会对当前的资源访问进行限流。
- 示例:直接修改 06-consumer-sentinel-flowcontrol-8080 工程的 DepartController 类
- 新增流控规则:
- 修改之前的流控规则:当对 qpsFlowRuleList 这个资源的访问达到了阈值1时,将会对 qpsFlowRule 这个资源进行限流
- 测试:使用Postman 并发请求 http://localhost:8080/consumer/depart/list,50个请求每个相隔200ms。
- 然后访问 http://localhost:8080/consumer/depart/get/1,验证下限流效果:
链路
- 当对一个资源有多种访问路径时,可以对某一路径的访问进行限流,而其它访问路径不限流。
- 示例:复制 02-provider-nacos-8081 工程,重命名为 06-provider-sentinel-flowcontrol-8081。
- 添加依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
<version>1.7.0</version>
</dependency>
<!-- sentinel 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 修改配置文件:
- 修改业务接口实现类:
- 修改 DepartController:
- 定义一个配置类:
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 默认情况下,Sentinel Web Filter收敛所有URL入口的Context
// 解释:例如 /list 和 /all 这两个接口对 qpsFlowRule 资源的访问,对于整个系统是区分不开的
// 这里设置关闭URL入口收敛功能,这样就可以区分
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
- 新增流控规则:先启动 06-provider-sentinel-flowcontrol-8081 工程
- 测试:分别刷新访问 http://localhost:8081/provider/depart/list 和 http://localhost:8081/provider/depart/all,看下限流效果。虽然 /list 和 /all 访问的是相同的资源,但是 /list 不发生限流,/all 发生限流。
流控效果分类
快速失败
- 快速失败,也称为直接拒绝,是默认的 QPS 流控超值处理方式。当 QPS 超过设置的阈值后,再来的请求将被直接拒绝,即抛出 FlowException。我们前面的流控使用的就是这种处理方式。
Warm Up
- 当系统中某 service 的 QPS 长期处于低水位运行状态时,系统为该 service 所分配的各种软硬件资源都会很少,例如,缓存空间、线程数量等资源。若 QPS 陡然增加可能会将系统一下压垮。为了避免这种情况的发生,我们希望 QPS 缓步增加到设定的阈值。这种应急情况的处理方式称为 warm up,即预热,也称为冷启动。
- sentinel 的 WarmUp 流控算法,采用的是 Guava 的 SmoothWarmingUp 算法,该算法继承自 SmoothRateLimiter 算法。不过,它们的算法思路是不同的:
- SmoothWarmingUp:其是通过不断缩短请求间的间隔来达到逐步提升访问量的目的。
- Sentinel 的 WarmUp:其是通过不断提升 QPS 来达到逐步提升访问量的目的。其算法更像是令牌桶限流算法。
排队等待
- 排队等待,也称为匀速排队。该方式会严格控制请求通过的间隔时间,让请求以均匀的速度通过。其是漏斗算法的改进。不同的是,当流量超过设定阈值时,漏斗算法会直接将再来的请求丢弃,而排队等待算法则是将请求缓存起来,后面慢慢处理。不过,该算法目前暂不支持 QPS 超过 1000 的场景。
- 其适合处理间隔性突发流量的场景。是削峰填谷效果的体现。
限流算法(回顾)
- 令牌桶限流算法:
- 漏斗限流算法
流控方案代码设置
- 直接修改 06-consumer-flowcontrol-code-8080 工程。
线程隔离流控
private static FlowRule threadFlowRule() {
FlowRule rule = new FlowRule();
rule.setResource("threadFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(20);
rule.setLimitApp("default");
return rule;
}
QPS 默认流控
private static FlowRule qpsFlowRule() {
FlowRule rule = new FlowRule();
rule.setRefResource("qpsFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rule.setLimitApp("default");
return rule;
}
关联流控模式
private static FlowRule qpsRelateFowRule() {
FlowRule rule = new FlowRule();
rule.setResource("qpsRelateFowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rule.setStrategy(RuleConstant.STRATEGY_RELATE);
rule.setRefResource("/list");
rule.setLimitApp("default");
return rule;
}
链路流控模式
private static FlowRule qpsChaniFlowRule() {
FlowRule rule = new FlowRule();
rule.setResource("qpsChaniFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rule.setStrategy(RuleConstant.STRATEGY_CHAIN);
rule.setRefResource("/provider/depart/list");
rule.setLimitApp("default");
return rule;
}
WarmUp 流控
private static FlowRule qpsWarmUpFlowRule() {
FlowRule rule = new FlowRule();
rule.setResource("qpsWarmUpFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rule.setCount(20);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule.setWarmUpPeriodSec(5);
return rule;
}
排队等待流控
private static FlowRule qpsQueueFlowRule() {
FlowRule rule = new FlowRule();
rule.setResource("qpsQueueFlowRule");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
rule.setCount(20);
rule.setMaxQueueingTimeMs(20 * 1000);
return rule;
}
来源流控
概念
- 来源流控:来源流控是针对请求发出者的来源名称所进行的流控。在流控规则中可以直接指定该规则用于限流的来源名称的请求,一条规则可以指定一个限流的来源。当然,若要指定多个来源,可以定义多个同名规则,也可以通过黑白名单规则一次性指定。
- 黑白名单:无论是黑名单还是白名单,其实就是一个请求来源名称列表,当然,该名单是属于某一具体资源的。出现在黑名单中的来源请求将被降级,其它来源的请求则可以正常进行访问;出现在白名单中的来源请求是可以进行正常访问的,而其它来源请求则将被降级。
定义原始请求解析器
- 创建工程:复制 06-consumer-sentinel-degrade-method-8080 工程,重命名为 06-consumer-sentinel-reqsource-8080。
- 定义解析器:
/**
* 定义原始请求解析器,其用于从请求中获取 来源标识
*/
@Component
public class DepartRequestOriginParser implements RequestOriginParser {
/**
* 该方法的返回值即为请求来源标识
*/
@Override
public String parseOrigin(HttpServletRequest request) {
String source = request.getParameter("source");
if (StringUtils.isEmpty(source)) {
return "serviceA";
}
// 返回的就是来源标识
return source;
}
}
- 修改配置文件:
流控规则中的来源指定
- 修改处理器:修改 06-consumer-sentinel-reqsource-8080 工程中的 DepartController:
- 启动提供者启动 04-provider-nacos-8081 工程
- dashbaord 指定来源:
- dashboard 指定同名规则:
- 以上规则的意思是,对于 serviceA 来源的请求,其 QPS 的阈值为 2;对于 serviceB 来源的请求,其 QPS 的阈值为 3;而其它来源的请求,其 QPS 的阈值为 10。但它们都是用于限制资源名称为 reqsourceRule 的资源的。
- 测试:并发访问 http://localhost:8080/consumer/depart/get/1?source=serviceA、http://localhost:8080/consumer/depart/get/1?source=serviceB、http://localhost:8080/consumer/depart/get/1?source=other。验证下流控效果。
动态设置黑白名单
- 流控规则中,一条规则仅可指定一个来源,若要指定多个来源,则需要定义多条同名规则,比较麻烦。可以通过黑白名单来一次性指定多个来源。
- dashboard 定义 QPS 流控规则:
- dashboard 定义授权规则:
- 测试:访问 http://localhost:8080/consumer/depart/get/1?source=serviceA、http://localhost:8080/consumer/depart/get/1?source=serviceB 是可以的,并且进行了流控 QPS = 2,但是其他的访问都直接降级了。
代码设置黑白名单
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Consumer068080 {
public static void main(String[] args) {
SpringApplication.run(Consumer068080.class, args);
initRule();
}
private static void initRule() {
List<AuthorityRule> rules = new ArrayList<>();
rules.add(reqSourceRule());
AuthorityRuleManager.loadRules(rules);
}
private static AuthorityRule reqSourceRule() {
AuthorityRule rule = new AuthorityRule();
rule.setResource("reqSourceRule");
rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
rule.setLimitApp("serviceA,serviceB");
return rule;
}
}