1. 降级规则
1.1 介绍
- 慢调用比例 (SLOW_REQUEST_RATIO):
- 选择以慢调用比例作为阈值,需要设置允许的 慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。
- 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
- 若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- 异常比例 (ERROR_RATIO):
- 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (ERROR_COUNT):
- 当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
1.2 慢调用比例(RT)
-
先在 Controller 中添加一个方法
@GetMapping("/testD") public String testD(){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("testD 测试RT"); return "------testD"; }
-
设置降级规则
-
使用 JMeter 进行压测
-
启动 JMeter ,去浏览器上访问该请求,查看结果
-
停止 JMeter 后,再访问该请求
1.3 异常比例
- 修改 Controller ,添加一个运行时异常
- 修改 降级规则
- 启动 JMeter ,去浏览器上访问该请求,查看结果
- 停止 JMeter 后,再访问该请求
1.4 异常数
- 修改 降级规则
- 连续发出请求,当刷新第六次的时候被熔断了
- 过一段时间后再次访问
2. @SentinelResource 详解
2.1 按资源名称限流 + 备选方案
- 启动 Nacos 控制台
- 启动 Sentinel 控制台
2.1.1 环境搭建
-
修改模块 :8401
-
修改 POM
<dependency> <groupId>com.demo.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency>
-
新建 Controller :RateLimitController
@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 服务不可用"); } }
-
启动项目
2.1.2 配置流控规则
- 当每秒访问该资源的请求数大于 1,则限流
2.1.3 额外问题
- 这时突然关闭 8401 服务,流控规则是否会消失?
2.2 按照Url地址限流 + 备选方案
-
修改 RateLimitController
@GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl() { return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002")); }
在 Controller 中添加这样一个方法
没有配置 blockHandler 限流之后的处理方法
-
启动 8401
-
配置流控规则
-
这里是将请求的 Url 作为限流的资源名
2.3 上面 两个配置备选方案的方式 的问题
-
系统默认的,没有体现我们自己的业务要求。
-
依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
-
每个业务方法都添加一个兜底的,那代码膨胀加剧。
-
全局统—的处理方法没有体现。
- 下面就来尝试解决一下这些 代码耦合、膨胀的问题
2.4 自定义限流处理逻辑
- 修改 alibaba-sentinel-service-8401
-
自定义一个限流处理类 :CustomerBlockHandler
public class CustomerBlockHandler { public static CommonResult handleException(BlockException exception) { return new CommonResult(2020, "自定义限流处理信息....CustomerBlockHandler"); } }
-
修改 Controller :RateLimitController
@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200,"客戶自定义 Handler",new Payment(2020L,"serial003")); }
-
重启 8401
-
配置限流规则
-
连续访问 :http://localhost:8401/rateLimit/customerBlockHandler
2.5 @SentinelResource 注解属性说明
-
value:资源名称,必需项(不能为空)
-
entryType:entry 类型,可选项(默认为 EntryType.OUT)
-
blockHandler: 处理BlockException的函数名称。函数要求:
1、必须是 public
2、返回类型与原方法一致
3、参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。
4、默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。 -
blockHandlerClass :存放blockHandler的类。对应的处理函数必须static修饰,否则无法解析,其他要求:同blockHandler。
-
fallback :fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
1、返回值类型必须与原函数返回值类型一致;
2、方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
3、fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 -
exceptionsToTrace :需要trace的异常
3. 服务熔断功能
- 先启动 Nacos 和 Sentinel
3.1 Ribbon系列
- 避免和前面的知识点混淆,这里重新创建一套环境
3.1.1 提供者 Provider-9003
-
新建模块 :alibaba-provider-Sentinel-Ribbon-9003
-
修改 POM
<dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.demo.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </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.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> </dependencies>
-
编写 YML
server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 #配置Nacos地址 management: endpoints: web: exposure: include: '*'
-
编写主启动类
@SpringBootApplication @EnableDiscoveryClient public class ProviderMain9003 { public static void main(String[] args) { SpringApplication.run(ProviderMain9003.class, args); } }
-
业务类
-
Controller
@RestController public class ProviderController { @Value("${server.port}") private String serverPort; public static HashMap<Long, Payment> hashMap = new HashMap<>(); static{ hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181")); hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182")); hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183")); } @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ Payment payment = hashMap.get(id); CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment); return result; } }
-
测试
启动 Sentinel、Nacos
启动 Provider-9003
访问 :http://localhost:9003/paymentSQL/1
- Provider-9003 搭建成功,复制配置,克隆出来一个 9004,具体操作之前有说过这就不多说了。
3.1.2 消费者 Consumer-84
-
新建模块 :alibaba-consumer-Sentinel-Ribbon-84
-
修改 POM
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.demo.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </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>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>
-
编写 YML
server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 service-url: nacos-user-service: http://nacos-payment-provider
-
编写主启动类
@SpringBootApplication @EnableDiscoveryClient public class ConsumerMain84 { public static void main(String[] args) { SpringApplication.run(ConsumerMain84.class, args); } }
-
业务类
-
Config :ApplicationContextConfig
@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
-
Controller
@RestController public class CircleBreakerController { @Value("${service-url.nacos-user-service}") private String SERVER_URL; @Autowired private RestTemplate restTemplate; @GetMapping(value = "/consumer/paymentSQL/{id}") @SentinelResource(value = "fallback") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ return restTemplate.getForObject(SERVER_URL+"/paymentSQL/"+id, CommonResult.class); } }
3.1.3 熔断降级配置
- 注意 :
- 热部署对java代码级生效及时
- 对 @SentinelResource 注解内属性,有时效果不好
- 所以需改了该注解中的内容,最好还是重启
-
在之前的 Consumer 的 Controller 中添加一段异常捕获的代码
// 异常捕捉 if (id == 4) { throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常...."); }else if (result.getData() == null) { throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常"); }
-
访问 :http://localhost:84/consumer/paymentSQL/4
因为之前在 Provider 中只定义了三条数据,没有 Id 为 4 的数据,所以肯定报错。
-
现在没有配置任何备选方案,直接把异常的代码展示给用户,很不友好。
只配置 fallback
- 下面开始配置,fallback
@SentinelResource(value = "fallback", fallback = "handlerFallBack") // fallback 只负责业务异常
/** FallBack 备选方案 */ public CommonResult handlerFallBack(@PathVariable("id") Long id, Throwable e){ Payment payment = new Payment(id, "null"); return new CommonResult<Payment>(444, "备选方案 —— handlerFallBack,异常内容" + e.getMessage(), payment); }
- 重启 Consumer-84 ,还访问 :http://localhost:84/consumer/paymentSQL/4
只配置 blockHandler
-
下面开始配置,blockHandler
// blockHandler 只负责 Sentinel 控制台配置的熔断降级规则 @SentinelResource(value = "fallback", blockHandler = "blockHandler")
/** blockHandler 备选方案 */ public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id,"null"); return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment); }
-
重启 Consumer-84
-
配置 Sentinel 降级规则
同时配置 fallBack 和 blockHandler
-
下面开始配置
@SentinelResource(value = "fallback", fallback = "handlerFallBack", blockHandler = "blockHandler")
-
重启 Consumer-84
-
配置 Sentinel 降级规则
配置 exceptionsToIgnore
-
配置
-
重启 Consumer-84
-
访问 :http://localhost:84/consumer/paymentSQL/4
3.2 Feign系列
3.2.1 环境配置
- 修改 Consumer-84 模块
-
修改 POM
添加 OpenFeign 依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
修改 YML
添加 Sentinel 对 Feign 的支持
feign: sentinel: enabled: true
-
修改主启动类
// 开启对 Feign 的支持 @EnableFeignClients
-
添加业务类
-
Feign 的业务接口 :FeignService
@FeignClient(value = "nacos-payment-provider", fallback = FeignFallbackService.class) public interface FeignService { @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }
-
对应的 FallBack 的 Service :FeignFallbackService
@Component public class FeignFallbackService implements FeignService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(44444,"服务降级返回,---FeignFallbackService",new Payment(id,"errorSerial")); } }
-
Controller :FeignController
@RestController public class FeignController { @Autowired private FeignService feignService; @GetMapping(value = "/consumer/feign/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { return feignService.paymentSQL(id); } }
-
启动测试
-
体验服务降级
关闭 Provider-9003、Provider-9004 服务
3.3 熔断降级框架对比
Sentinel | Hystrix | Resilience4j | |
---|---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离 / 信号量隔离 | 信号量隔离 |
熔断降级策略 | 基于响应时间、异常比、异常数 | 基于异常比 | 基于异常比、响应时间 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
扩展性 | 多个扩展点 | 插件形式扩展 | 接口形式扩展 |
限流 | 基于 QPS、支持基于调用关系的限流 | 有限支持 | Rate Limter |
流量整形 | 支持预热、匀加速、预热排队模式 | 不支持 | 简单的 Rate Limiter |
系统自适应保护 | 支持 | 不支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可以配置规则、查看妙计监控、机器发现等 | 简单的监控查看 | 不提供控制台,可对接其他监控系统 |
基于注解的支持 | 支持 | 支持 | 支持 |
4. Sentinel 规则持久化
- 一旦我们重启应用,Sentinel规则将消失
- 生产环境需要将配置规则进行持久化
- 将限流配置规则持久化进 Nacos 保存
- 只要刷新 8401 某个 rest 地址,sentinel 控制台的流控规则就能看到,只要 Nacos 里面的配置不删除,针对 8401 上 Sentinel 上的流控规则持续有效
- 具体使用步骤 :
- 修改 alibaba-sentinel-service-8401
-
修改 POM,添加相关依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
-
修改 YML,添加 Nacos 业务规则配置
datasource: ds1: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
-
添加 Nacos 业务规则配置
配置内容 :
[ { "resource": "/retaLimit/byUrl", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
-
启动 alibaba-sentinel-service-8401、Sentinel
-
然后去 Sentinel 控制台刷新,查看流控规则
- 停止 8401 服务,刷新 Sentinel,刚刚的流控规则消失了
- 再次启动 8401 ,刷新 Sentinel,刚刚的流控规则又回来了