此文章主要讲解springcloud中的服务熔断Hystrix的替换方案Sentinel的相关知识。
服务熔断Hystrix的替换方案Sentinel
概述
- 2018年底Netflix公司宣布Hystrix已经足够稳定,不再积极开发Hystrix,该项目处于维护模式。就目前来看Hystrix是比较稳定的,并且Hystrix只是停止开发新的版本,并不是完全停止维护,Bug什么的依然会维护。因此,短期内,Hystrix依然是能继续使用的。但是从长远看,Hystrix总会达到它的生命周期,那么Spring Cloud生态中是否有替代产品呢?
替换方案介绍
Alibaba Sentinel
- Sentinel是阿里开源的一款熔断器的实现,目前在Spring Cloud的孵化器项目Spring Cloud Alibaba中的一员Sentinel本身在阿里内部已经被大规模采用,非常稳定。因此,可以作为一个很好的替代品。
Resilience4J
- Resilience4J是一款轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此,Resilience4J还原生支持SpringBoot 1.x/2.x,而且监控也不像Hystrix一样弄Dashboard/Hystrix等一堆*,而是支持和micrometer、prometheus以及Dropwizard metrics进行整合。
Sentinel概述
Sentinel官网
Sentinel是什么
-
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
-
Sentinel 具有以下特征:
-
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
-
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
去哪里下
- 下载地址。
能干嘛
安装和运行Sentinel Dashboard
Sentinel的组成
-
Sentinel 的组成可以分为两个部分:
-
核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配 )。
-
控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。
安装和运行步骤
-
下载。
-
运行命令:
# 直接启动
java -jar sentinel-dashboard-1.7.1.jar
# -Dserver.port=8080用于指定Sentinel控制台端口为8080
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
- Sentinel的登录界面(访问地址默认是 http://localhost:8080/ ,用户名和密码为sentinel/sentinel):
初始化演示工程
启动Nacos服务注册中心和配置中心
startup.cmd -m standalone # 单机启动
# 访问 http://localhost:8848/nacos/
搭建工程
新建模块
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>spring_cloud_atguigu_2020</artifactId>
<groupId>com.itjing.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<dependencies>
<!-- 后续做Sentinel的持久化会用到的依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Open Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<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>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>
<!-- 引入自己定义的api通用包-->
<dependency>
<groupId>com.itjing.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
YML文件
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# nacos服务注册中心
server-addr: localhost:8848
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: localhost:8080
# 默认8719 ,如果端口被占用,端口号会自动 +1,提供给 sentinel 的监控端口,直到找到没有被占用的端口
port: 8719
# 取消懒加载
eager: true
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: '*'
主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
业务逻辑
@RestController
public class FlowLimitController {
@GetMapping(value = "/testA")
public String testA() {
return "-------testA--------";
}
@GetMapping(value = "/testB")
public String testB() {
return "~~~~~~~~testB~~~~~~~~";
}
}
启动 8401。
流控规则
基本介绍
-
资源名:
唯一名称,默认请求路径。 -
针对来源:
Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。 -
阈值类型/单机阈值:
-
-
QPS(每秒请求数量):
当调用该API的QPS达到阈值的时候,进行限流。
-
-
-
线程数:
当调用该API的线程数达到阈值的时候,进行限流。
-
-
是否集群:
不需要集群。 -
流控模式:
-
-
直接:
API达到限流条件时,直接限流。
-
-
-
关联:
当关联的资源达到阈值的时候,就限流自己。
-
-
-
链路:
只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)[API级别的针对来源]。
-
-
流控效果:
-
-
快速失败:
直接失败,抛异常。
-
-
-
Warm Up:
根据codeFactor(冷加载因子,默认为3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
-
-
-
排队等待:
匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
-
流控模式之直接(默认直接–>快速失败)
配置说明
表示1秒内查询1次就是OK,如果超过1次,就直接–>快速失败,报默认错误。
测试
-
快速点击 http://localhost:8401/testA 。
-
结果显示:
流控模式之关联
是什么?
-
当和A关联的资源B达到阈值的时候,就限流A自己。
-
比如:电商系统中,下订单后面的流程就是支付,一旦支付超过阈值,就需要限制下订单。
配置说明
当关联资源/testB的QPS的阈值超过1的时候,就限流A的访问。
测试
通过 Postman 并发访问 http://localhost:8401/testB ,然后通过浏览器访问 http://localhost:8401/testA 。B的并发访问,导致A挂了。
流控模式之链路
配置说明
- 多个请求调用 http://localhost:8401/testA ,如果1秒内请求次数超过1次,就会触发限流。
流控效果之预热
概述
-
Warm Up(
RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。 -
公式:阈值除以coldFactor(冷加载因子,默认为3),经过预热时长后才会达到阈值。
配置说明
-
系统初始化的的阈值是10/3约等于3,即阈值开始为3;经过5秒后阈值才慢慢升高恢复到10。
,即允许有缓冲的场景。
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
流控效果之排队等待
概述
配置说明
- http://localhost:8401/testA 每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
降级规则
基本介绍
- 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的
弱依赖服务调用
进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
降级规则之RT
概述
平均响应时间超出阈值
且在时间窗口内通过的请求>=5
,两个条件同时满足后触发降级窗口期过后关闭断路器
RT最大4900 (更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
代码
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping(value = "/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
log.info("testD 测试RT");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "-------testD--------";
}
}
配置
测试
- 按照上述配置,永远是1秒钟10个进程调用了testD,但是我们希望200ms内处理完本地任务,所以一直处于熔断状态,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸),微服务不可用。后续停止jemter,没有那么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK。
降级规则之异常比例
概述
- 异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。
代码
@GetMapping(value = "/testD")
public String testD() {
//模拟异常
int num = 10 / 0;
return "-------testD--------";
}
配置
当单位统计时长内请求数量大于最小请求数目 5,并且异常比例大于阈值(0.2),则接下来的熔断时间内请求自动会被熔断。经过时间窗口时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
按照上述配置,单独访问一次,必然访问一次,报错一次,因为没有达到服务熔断的要求。开启Jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了,断路器开启,微服务不可用了,不再报错而是服务降级了。
降级规则之异常数
概述
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
配置
第一次访问: http://localhost:8401/testD 肯定报错,因为除数不能为0,但是达到5次报错后,进入熔断后降级。
热点key限流
基本介绍
-
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
-
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
-
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
-
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
- Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
@SentinelResource注解
-
熔断降级方法分为系统默认和用户自定义两种,
@SentinelResource 注解和 Hystrix的@HystrixCommand 注解类似
,都是用来配置用户自定义的熔断降级方法。 -
@SentinelResource注解和@HystrixCommand不同的是,
只处理Sentinel控制台配置的违规情况,并不处理异常
应用示例
代码
@GetMapping(value = "/testHotKey")
//通过blockHandler指定熔断降级的方法,通过fallback指定触发异常执行的降级方法
@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")
public String testHotKey(
@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "testHotKey";
}
/**
* 熔断降级
*
* @param p1
* @param p2
* @param exception
* @return
*/
public String dealTestHotKey(String p1, String p2, BlockException exception) {
return "--------熔断降级-------";
}
配置
访问: http://localhost:8401/testHotKey?p1=aaa ,当访问频率超出阈值,则进行限流。
如果testHotKey的参数p1的值不是5,那么QPS是1;如果参数p1的值是5,那么QPS是2000。
测试
-
测试 http://localhost:8401/testHotKey?p1=1 请求,如果每秒请求1次,不会熔断降级,如果每次请求超过1次,会触发熔断降级。
-
测试 http://localhost:8401/testHotKey?p1=5 请求,只要每秒请求不超过2000次,就不会熔断降级。
系统规则
基本介绍
- Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
背景
-
在开始之前,我们先了解一下系统保护的目的:
-
- 保证系统不被拖垮
-
- 在系统稳定的前提下,保持系统的吞吐量
-
长期以来,系统保护的思路是根据硬指标,即系统的负载 (load1) 来做系统过载保护。当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入。这个思路给我们带来了不可避免的两个问题:
-
- load 是一个“结果”,如果根据 load 的情况来调节流量的通过率,那么就始终有延迟性。也就意味着通过率的任何调整,都会过一段时间才能看到效果。当前通过率是使 load 恶化的一个动作,那么也至少要过 1 秒之后才能观测到;同理,如果当前通过率调整是让 load 好转的一个动作,也需要 1 秒之后才能继续调整,这样就浪费了系统的处理能力。所以我们看到的曲线,总是会有抖动。
-
- 恢复慢。想象一下这样的一个场景(真实),出现了这样一个问题,下游应用不可靠,导致应用 RT 很高,从而 load 到了一个很高的点。过了一段时间之后下游应用恢复了,应用 RT 也相应减少。这个时候,其实应该大幅度增大流量的通过率;但是由于这个时候 load 仍然很高,通过率的恢复仍然不高。
-
TCP BBR 的思想给了我们一个很大的启发。我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标(系统 load)来做限流。最终我们追求的目标是
在系统不被拖垮的情况下,提高系统的吞吐率,而不是 load 一定要到低于某个阈值
。如果我们还是按照固有的思维,超过特定的 load 就禁止流量进入,系统 load 恢复就放开流量,这样做的结果是无论我们怎么调参数,调比例,都是按照果来调节因,都无法取得良好的效果。 -
Sentinel 在系统自适应保护的做法是,用 load1 作为启动自适应保护的因子,而允许通过的流量由处理请求的能力,即请求的响应时间以及当前系统正在处理的请求速率来决定。
系统规则
-
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
-
系统保护规则是应用整体维度的,而不是资源维度的,并且
仅对入口流量生效
。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。 -
系统规则支持以下的模式:
-
-
Load 自适应
(仅对 Linux/Unix-like 机器生效
):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
。
-
-
-
CPU usage
(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
-
-
-
平均 RT
:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
-
-
-
并发线程数
:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
-
-
-
入口 QPS
:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
-
系统规则配置界面
@SentinelResource讲解
修改模块
cloudalibaba-sentinel-service8401
新增Controller
@RestController
public class RateLimitController {
@GetMapping("/byResource") //处理降级的方法名
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按照资源名限流测试0K", new Payment(2020L, "serial001"));
}
//降级方法
public CommonResult handleException(BlockException e) {
return new CommonResult(444, e.getClass().getCanonicalName() + "\t 服务不可用");
}
}
很明显,上面虽然自定义了兜底方法,但是耦合度太高,下面要解决这个问题。
自定义全局BlockHandler处理类
写一个 CustomerBlockHandler 自定义限流处理类:
Sentinel整合Ribbon系列
准备工作
- 需要启动Nacos和Sentinel。
服务提供者
新建模块
cloudalibaba-provider-payment9003
cloudalibaba-provider-payment9004
POM文件
<dependencies>
<!-- springcloud alibaba nacos 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<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>
<!-- 日常通用jar包 -->
<!-- <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<!-- 引入自己定义的api通用包-->
<dependency>
<groupId>com.itjing.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
YML文件
注意改端口号就行
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置Nacos的地址
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: '*'
主启动类
编写主启动类。
业务类
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
//模拟sql查询的数据
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static {
hashMap.put(1L, new Payment(1L, "xcxcxcxcxcxcxcxcxcxcxcxcxc11111111"));
hashMap.put(2L, new Payment(2L, "xcxcxcxcggggggggg2222222222222222"));
hashMap.put(3L, new Payment(3L, "xcxcxcxccxxcxcfafdgdgdsgdsgds33333"));
}
@GetMapping("/payment/{id}")
public CommonResult paymentSql(@PathVariable("id") Long id) {
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
return result;
}
}
服务消费者
新建模块
cloudalibaba-consumer-nacos-order84
POM文件
<dependencies>
<!-- 后续做Sentinel的持久化会用到的依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- springcloud alibaba nacos 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- springboot整合Web组件 -->
<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>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>
<!-- 引入自己定义的api通用包-->
<dependency>
<groupId>com.itjing.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
yaml文件
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置Nacos的地址
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: localhost:8080
# 默认8719 ,如果端口被占用,端口号会自动 +1,提供给 sentinel 的监控端口,直到找到没有被占用的端口
port: 8719
# 取消懒加载
eager: true
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: '*'
# 消费者将要去访问的微服务名称(成功注册进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
主启动类
略
业务类
配置类
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Controller
@RestController
@Slf4j
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String serverURL;
@Resource
private RestTemplate restTemplate;
@GetMapping("/consutomer/fallback/{id}")
public CommonResult fallback(@PathVariable("id") Long id) {
if (id >= 4) {
throw new IllegalArgumentException("非法参数异常...");
} else {
return restTemplate.getForObject(serverURL + "/payment/" + id, CommonResult.class);
}
}
}
上面只实现了 以 nacos 作为服务注册中心,消费者使用 ribbon 实现负载均衡调用提供者的效果。
进行相关配置
在消费者中操作
只配置 fallback
@GetMapping("/consutomer/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
public CommonResult fallback(@PathVariable("id") Long id) {
CommonResult result = restTemplate.getForObject(serverURL + "/payment/" + id, CommonResult.class);
if (id >= 4) {
throw new IllegalArgumentException("非法参数异常...");
} else if (result == null) {
throw new NullPointerException("没有记录");
}
return result;
}
//兜底方法
public CommonResult handleFallback(@PathVariable("id") Long id, Throwable e) {
return new CommonResult(414, "---fallback--", e);
}
业务异常
会被 fallback 处理,返回我们自定义的提示信息,而如果给它加上流控,并触发阈值,只能返回sentinel默认的提示信息
。
只配置 blockHandler
@GetMapping("/consutomer/fallback/{id}")
//@SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
@SentinelResource(value = "fallback", blockHandler = "handleblockHandler") //只负责sentinel控制台配置违规
public CommonResult fallback(@PathVariable("id") Long id) {
CommonResult result = restTemplate.getForObject(serverURL + "/payment/" + id, CommonResult.class);
if (id >= 4) {
throw new IllegalArgumentException("非法参数异常...");
} else if (result == null) {
throw new NullPointerException("没有记录");
}
return result;
}
public CommonResult handleblockHandler(@PathVariable("id") Long id, BlockException e) {
return new CommonResult(414, "---blockHandler--", e);
}
这时候的效果就是,运行异常直接报错错误页面。在sentinel上添加一个降级规则,设置2s内触发异常2次,触发阈值以后,返回的是我们自定义的 blockhanlder 方法返回的内容。
两者都配置
@GetMapping("/consutomer/fallback/{id}")
//@SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
//@SentinelResource(value = "fallback", blockHandler = "handleblockHandler")
@SentinelResource(value = "fallback", blockHandler = "handleblockHandler",fallback = "handleFallback")
public CommonResult fallback(@PathVariable("id") Long id) {
CommonResult result = restTemplate.getForObject(serverURL + "/payment/" + id, CommonResult.class);
if (id >= 4) {
throw new IllegalArgumentException("非法参数异常...");
} else if (result == null) {
throw new NullPointerException("没有记录");
}
return result;
}
//====fallback
public CommonResult handleFallback(@PathVariable("id") Long id, Throwable e) {
return new CommonResult(414, "---fallback--", e);
}
//====blockHandler blockHandler的方法必须有这个参数
public CommonResult handleblockHandler(@PathVariable("id") Long id, BlockException e) {
return new CommonResult(414, "---blockHandler--", e);
}
明显两者都是有效的,可以同时配置。
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
异常忽略
抛出某异常时,不需要进行兜底处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4A3NnpW-1622078335648)(https://gitee.com/xiaojinggege/blogImage/raw/master/img/image-20210122172327682.png)]
Sentinel整合Feign系列
修改 84 模块
84 消费者调用提供者 9003
Feign组件一般是消费侧
POM文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
YML文件
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
主启动类
添加注解 :@EnableFeignClients
激活 open-feign
Service接口
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) // 实现降级
public interface PaymentService {
@GetMapping("/payment/{id}")
public CommonResult paymentSql(@PathVariable("id") Long id);
}
Service接口实现类实现降级
@Component // 不要忘记
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult paymentSql(Long id) {
return new CommonResult(414, "open-feign 整合 sentinel 实现的全局服务降级策略", null);
}
}
Controller
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/payment/{id}")
public CommonResult paymentSql(@PathVariable("id") Long id) {
return paymentService.paymentSql(id);
}
测试
启动相关组件和微服务。
正常访问。
测试,关闭提供者9003微服务,会触发自动降级,不会被耗死。
Sentinel规则持久化
是什么?
- 一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。
怎么玩?
- 将Sentinel中的规则持久化到Nacos保存,只要Nacos里面的配置不删除,那么Sentinel中的规则将持续有效。
步骤(修改服务消费者)
POM文件
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML文件
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置Nacos的地址
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: localhost:8080
# 默认8719 ,如果端口被占用,端口号会自动 +1,提供给 sentinel 的监控端口,直到找到没有被占用的端口
port: 8719
# 将Sentinel的规则持久化到Nacos中
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data_type: json
rule_type: flow
# 取消懒加载
eager: true
# 暴露监控端点
management:
endpoints:
web:
exposure:
include: '*'
# 消费者将要去访问的微服务名称(成功注册进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
在Nacos中添加业务规则配置
去nacos上创建一个dataid ,名字和 yml 配置的一致,比如我这里是 nacos-order-consumer ,json格式,内容如下
[
{
"resource": "/testA",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource:# 资源名称。
limitApp:# 来源应用。
grade:# 阈值类型,0表示线程数,1表示QPS。
count:# 单机阈值。
strategy:# 流控模式,0表示直接,1表示关联,2表示链路。
controlBehavior:# 流控效果,0表示快速失败,1表示Warm up,2表示排队等待。
clusterMode:# 是否集群。
启动应用,然后刷新 Sentinel,发现存在 关于 /testA 请求路径的流控规则。
停止 8401 在看sentinel,发现里面没有内容,当多次调用接口,关于接口的流控规则又出现了,持久化验证成功。