SpringCloudAlibaba-熔断_限流(Sentinel)
Sentinel
概述
https://github.com/alibaba/sentinel
https://sentinelguard.io/zh-cn/docs/introduction.html
有Hystrix,为什么需要Sentinel?
Hystrix
1、Hystrix需要自己搭建监控平台
2、Hystrix没有一套web界面给我们进行 更细粒度化的配置(流量监控、速率监控、服务熔断、服务降级...)
Sentinel
1、单独的组件
2、界面化的细粒度配置
what
1、Sentinel 是面向分布式、多语言异构化服务架构的 流量治理组件;
主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性;
Sentinel基本概念
资源
资源是 Sentinel 的关键概念;
可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码;
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来;
大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源;
规则
围绕资源的实时状态设定的规则;
可以包括流量控制规则、熔断降级规则以及系统保护规则;
所有规则可以动态实时调整;
How
安装Sentinel控制台
Sentinel 的使用可以分为两个部分:
- 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
- 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
https://github.com/alibaba/Sentinel/releases
运行前提
jdk8以上环境、8080端口不能被占用
java -jar xxxx.jar
访问 http://localhost:8080/#/login
默认 账号/密码 sentinel/sentinel
创建8401微服务 且被 Sentinel监控
<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> server: port: 8401 spring: application: name: sentinel cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 #sentinel dashboard地址 port: 8719 #默认端口,如被占用,从8719+1开始,直至找到未被使用的端口 management: endpoints: web: exposure: include: '*' @EnableDiscoveryClient @SpringBootApplication public class Sentinel8401 { public static void main(String[] args) { SpringApplication.run(Sentinel8401.class, args); } } @RestController public class FlowLimitController { @GetMapping(value = "/testA") public String testA(){ return "testA"; } @GetMapping(value = "/testB") public String testB(){ return "testB"; } }
Sentinel采用懒加载,只有访问服务,在Sentinel dashboard才能看到;
流控规则(流量控制)
https://sentinelguard.io/zh-cn/docs/flow-control.html
概念
阈值类型
QPS:
每秒请求数;
当QPS达到阈值,进行限流;
线程数:
支持实际处理任务的线程数;
当实际请求的线程数 > 支持的线程数,进行限流;
流控模式
直接(默认)
直接 -> 快速失败 (系统默认)
/testB达到阈值,进行限流
关联
testA达到阈值,限流testB
流控效果
快速失败
直接抛异常
warm up
阈值/coldFactor(默认3),经过预热时长,达到阈值
10/3 经过5秒 达到阈值10
排队等待
匀速排队;
严格控制请求通过的时间间隔;
对应 漏桶算法;
每秒均匀处理一个请求
降级规则
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
概念
Sentinel的断路器没有 半开状态;
How
RT
testC 必须在200ms内处理响应,否则开启熔断,经过2s窗口期,关闭熔断
异常比例
testC 异常比例>0.2,开启熔断,经过2s窗口期,关闭熔断;
异常数
testC 单位时间内异常次数达到3次,开启熔断,经过61s窗口期,关闭熔断;
热点Key限流
https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html
@SentinelResource的blockHandler 仅处理后台配置出现的异常;
How
@GetMapping(value = "/testD") @SentinelResource(value = "testDResource", blockHandler = "handle_testD") public String testD(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2){ return "---testD"; } public String handle_testD(String p1,String p2, BlockException blockException){ return "o(╥﹏╥)o handle_testD"; }
http://localhost:8401/testD?p1=a 当p1参数达到阈值,开启熔断
http://localhost:8401/testD?p1=a&p2=b 当p1参数达到阈值,开启熔断
http://localhost:8401/testD?p2=b 无论是否达到阈值,都不会开启熔断
testDResource 资源下标为0的参数 QPS>1 时,熔断开启,服务降级处理handle_testD,经过1s窗口期,熔断关闭;
参数例外项
当某个参数值是指定值时,期望该参数的阈值可以提升至某个值;
testDResource 资源下标为0的参数 QPS>1 时,熔断开启;
当 下标为0的参数值=5,QPS<=200;
系统规则
https://sentinelguard.io/zh-cn/docs/system-adaptive-protection.html
阈值类型
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的
maxQps * minRt
计算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
谨慎使用
@SentinelResource
资源名称限流 + blockHandle
@GetMapping(value = "/byResource") @SentinelResource(value = "byResourceResource", blockHandler = "handle_byResource") public String testE(@RequestParam(value = "p1", required = false) String p1){ return "---testD"; } public String handle_byResource(String p1, BlockException blockException){ return "o(╥﹏╥)o handle_byResource" + blockException.getClass().getCanonicalName(); }
byResourceResource QPS>2时开启熔断,handle_byResource进行熔断处理;
URL限流 + blockHandle
@GetMapping(value = "/byURL") @SentinelResource(value = "byURL") public String byURL(@RequestParam(value = "p1", required = false) String p1){ return "---byURL"; }
/byURL QPS>1时,开启熔断;
目前存在的问题
1、系统默认的,没有体现业务要求
2、自定义处理 与 业务代码耦合
3、每个业务方法都要建一个 处理方法,代码臃肿
4、全局统一的处理没有体现
自定义BlockHandler
public class MyBlockHandler { public static String handler1(BlockException blockException){ return "---handler1 o(╥﹏╥)o"; } public static String handler2(String p1, BlockException blockException){ return "---handler2 o(╥﹏╥)o"; } }
@GetMapping(value = "/myBlockHandle") @SentinelResource(value = "myBlockHandleResource" ,blockHandlerClass = MyBlockHandler.class, blockHandler = "handler2") public String myBlockHandle(@RequestParam(value = "p1", required = false) String p1){ return "---myBlockHandle"; }
@SentinelResource详解
https://sentinelguard.io/zh-cn/docs/annotation-support.html
服务熔断
nacos+ribbon+sentinel
provider
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> server: port: 9001 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848 management: endpoints: web: exposure: include: '*' @SpringBootApplication @EnableDiscoveryClient public class NacosProviderDemoApplication9001 { public static void main(String[] args) { SpringApplication.run(NacosProviderDemoApplication9001.class, args); } } @RestController public class EchoController { @Value(value = "${server.port}") private String port; @GetMapping(value = "/echo/{string}") public String echo(@PathVariable String string) { return "Hello Nacos Discovery " + string + "port:" + port; } }
consumer
<!-- spring-cloud-starter-alibaba-nacos-discovery 集成了ribbon --> <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> server: port: 84 spring: application: name: nacos-ribbon-cosumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 #sentinel dashboard地址 port: 8719 #默认端口,如被占用,从8719+1开始,直至找到未被使用的端口 service-url: #消费者要调用的服务名称 nacos-user-service: http://nacos-provider @Configuration public class Config { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } } @EnableDiscoveryClient @SpringBootApplication public class NacosConsumer84 { public static void main(String[] args) { SpringApplication.run(NacosConsumer84.class, args); } } @RestController public class Controller { @Value(value = "${service-url.nacos-user-service}") private String serviceUrl; @Autowired private RestTemplate restTemplate; @GetMapping(value = "/nacos/consumer/{s}") @SentinelResource( value = "test", fallback = "handleTestFallback", //fallback负责运行时异常 blockHandler = "handleTestBlockHandler", //blockHandler负责sentinel平台配置规则 exceptionsToIgnore = {NullPointerException.class} //指定异常将不会被 fallback或blockHandler ) public String test(@PathVariable(value = "s") String s){ if (s.equalsIgnoreCase("a")){ throw new IllegalArgumentException("IllegalArgumentException..."); } else if (s.equalsIgnoreCase("b")){ throw new NullPointerException("NullPointerException..."); } return restTemplate.getForObject(serviceUrl + "/echo/"+s, String.class); } public String handleTestFallback(String s, Throwable e){ return "handleTestFallback...s="+s + "msg="+e.getMessage(); } public String handleTestBlockHandler(String s, BlockException e){ return "handleTestBlockHandler...s="+s + "msg="+e.getMessage(); } }
nacos+openfeign+sentinel
provider
如上
consumer
<dependencies> <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> <!-- spring-cloud-starter-alibaba-nacos-discovery 集成了ribbon --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> </dependencies> server: port: 85 spring: application: name: nacos-ribbon-cosumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 #sentinel dashboard地址 port: 8719 #默认端口,如被占用,从8719+1开始,直至找到未被使用的端口 feign: sentinel: enabled: true #激活sentinel对feign支持 @FeignClient(value = "nacos-provider", fallback = MyFeignClientFallBack.class) public interface MyFeignClient { @GetMapping(value = "/echo/{s}") String test(@PathVariable(value = "s") String s); } @Component public class MyFeignClientFallBack implements MyFeignClient{ public String test(String s) { return "myFeignClientFallBack..."; } } @RestController public class Controller { @Autowired private MyFeignClient myFeignClient; @GetMapping(value = "/nacos/consumer/{s}") @SentinelResource( value = "test", fallback = "handleTestFallback", //fallback负责运行时异常 blockHandler = "handleTestBlockHandler", //blockHandler负责sentinel平台配置规则 exceptionsToIgnore = {NullPointerException.class} //指定异常将不会被 fallback或blockHandler ) public String test(@PathVariable(value = "s") String s){ if (s.equalsIgnoreCase("a")){ throw new IllegalArgumentException("IllegalArgumentException..."); } else if (s.equalsIgnoreCase("b")){ throw new NullPointerException("NullPointerException..."); } return myFeignClient.test(s); } public String handleTestFallback(String s, Throwable e){ return "handleTestFallback...s="+s + "msg="+e.getMessage(); } public String handleTestBlockHandler(String s, BlockException e){ return "handleTestBlockHandler...s="+s + "msg="+e.getMessage(); } }
熔断框架对比
sentinel规则持久化
why
应用重启后,sentinel配置的规则将会全部消失;
How
将sentinel配置的规则持久化到nacos
<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> <!-- sentinel规则持久化到nacos--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> server: port: 8401 spring: application: name: sentinel cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 #sentinel dashboard地址 port: 8719 #默认端口,如被占用,从8719+1开始,直至找到未被使用的端口 datasource: #sentinel规则持久化到nacos数据库配置 ds1: nacos: server-addr: localhost:8848 dataId: ${spring.application.name} #应用名称 groupId: DEFAULT_GROUP data-type: json rule-type: flow management: endpoints: web: exposure: include: '*' @GetMapping(value = "/byResource") public String testE(@RequestParam(value = "p1", required = false) String p1){ return "---testD"; } @EnableDiscoveryClient @SpringBootApplication public class Sentinel8401 { public static void main(String[] args) { SpringApplication.run(Sentinel8401.class, args); } }
nacos-server后台规则配置