git地址: https://github.com/alibaba/Sentinel
中文文档: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
sentinel实际和hystrix的作用一样,实现服务降级、熔断等。但是hystrix的不足之处大概有:1.需要程序员手工搭建监控平台;2.没有一套web界面可以给我们进行细粒度化的配置。Sentinel也是实现流量控制、速率控制、服务熔断、服务降级。Sentinel有的优点如下:1.单独的组件,可以独立出来。2.直接界面化的细粒度统一配置。
1.下载安装
1.下载jar包
到git https://github.com/alibaba/Sentinel/releases 下载即可。例如我下载的是:
sentinel-dashboard-1.8.0.jar
2. 启动
直接以jar包的方式运行即可。
java -jar ./sentinel-dashboard-1.8.0.jar
3.访问即可
默认的端口是8080,启动后访问,登录: sentinel/sentinel
2. 新建项目使用sentinel初始化监控
新建一个项目,注册中心为nacos,端口8848; 熔断流量监控使用sentinel,端口8080.
1.新建项目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>cloud</artifactId> <groupId>cn.qz.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-sentinel-service8401</artifactId> <dependencies> <!--引入自己抽取的工具包--> <dependency> <groupId>cn.qz.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!--SpringCloud ailibaba sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- SpringBoot整合Web组件+actuator --> <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> </project>
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地址 # 默认就是8719端口,如果占用会依次+1开始扫描 port: 8719 management: endpoints: web: exposure: include: '*'
4.主启动类
package cn.qz.cloud.alibaba; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @Author: qlq * @Description * @Date: 22:06 2020/11/25 */ @EnableDiscoveryClient @SpringBootApplication public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class, args); } }
5.测试Controller
package cn.qz.cloud.alibaba.controller; import cn.qz.cloud.utils.JSONResultUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author: qlq * @Description * @Date: 22:07 2020/11/25 */ @RestController public class FlowLimitController { @GetMapping("/testA") public JSONResultUtil<String> testA() { return JSONResultUtil.successWithData("testA"); } @GetMapping("/testB") public JSONResultUtil<String> testB() { return JSONResultUtil.successWithData("testB"); } }
6.启动后到nacos查看服务列表
7. 访问sentinel
需要注意sentinel采用的是懒加载,如果直接启动服务没有访问,sentinel是不会拦截到请求的。所以需要访问几次服务然后到sentinel中查看。sentinel控制台如下:
3. sentinel流控
1.实时监控
可以实时的查看每个请求的访问QPS情况。
2.簇点链路
和上面1差不多。展示最近的访问情况。
3.流控规则
新增流控规则的时候,界面如下:
概念性问题:
- 资源名:唯一名称,默认请求路径
- 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型/单机阈值
(1)QPS(每秒钟的请求数):当掉该api达到阈值的时候,进行限流
(2)线程数: 当调用该API的线程数达到阈值时,进行限流
- 是否集群:不需要集群
- 流控模式
(1)直接:直接限流
(2)关联:当关联的资源达到阈值时,限流自己
(3)链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
- 流控效果
(1)快速失败:直接失败,抛异常
(2)Warm up:根据code Factor(冷加载印子,默认3)的值,从阈值/cedeFactor,经过预热时长,才达到预设的QPS
(3)排队等待: 匀速排队,让请求匀速的通过,阈值类型必须设置QPS。否则无效。
4. 流控模式使用
1. QPS 快速失败
多次访问查看结果如下:
2. 线程数直接失败
这个是接收请求,但是当处理的线程数超过1的时候报上面的【Blocked by Sentinel (flow limiting)】。和上面不同的是这个允许请求进去,上面是达到阈值就不让请求进去。
3.关联流控
关联流控就是关联的资源达到阈值,限流自己。比如如下:当B超过QPS为1之后,A限流:
测试方法:
(1) 使用postman或者jmeter并发访问testB,如下:加到collections中,然后没300ms访问一次,访问30次
(2)访问A发现A阻塞
4. 链路流控规则
(1)查看簇点链路,/testA的资源入口是sentinel_web_servlet_context
(2) 对A进行链路流控
(3)频繁访问会报Blocked by Sentinel (flow limiting)
5. 流控效果
1.直接快速失败
默认的就是快速失败,抛出阻塞信息。实现类是 com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
2.warm up预热: com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController.WarmUpController
根据code Factor(冷加载印子,默认3)的值,从阈值/cedeFactor,经过预热时长,才达到预设的QPS。比如下面规则表示:初始的时候阈值是10/3 = 3 ;5s的预热时间达到10。
这种模式的应用场景是秒杀系统,开启瞬间会有很多流量上来,很有可能把系统打死,预热就是为了保护系统,让流量缓慢的进来。
3. 排队等待
匀速排队,让请求匀速的通过。这种很好理解,就是每秒钟放行多少个请求。如下:每秒钟处理1个,超过的话进行排队;排队时长超过2s报阻塞错误。源码是com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController。采用的算法是令牌桶算法。
4.sentinel熔断降级
参考: https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
提供以下几种熔断策略:
(1)慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
(2)异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
(3)异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
1. 慢调用比例模式
满足两个条件会触发熔断:
1》单位统计时长请求数大于设置的最小值。下面是每秒钟5个。
2》慢请求达到设置的比例。
(1)新增方法
@GetMapping("/testD") public JSONResultUtil<String> testD() throws InterruptedException { TimeUnit.SECONDS.sleep(1); return JSONResultUtil.successWithData("testD"); }
(2)jmeter 以每秒钟10次请求访问
(3)curl访问
$ curl http://localhost:8401/testD % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 35 0 35 0 0 249 0 --:--:-- --:--:-- --:--:-- 280Blocked by Sentinel (flow limiting)
(4)上面jmeter每秒钟10次请求大于最小值5;每个请求处理时长超过200ms,满足第二个条件,因此触发降级。
2. 异常比例模式
如下:这个也是需要两个条件。
1》每秒钟请求超过五个
2》异常比例超过50%触发熔断
(1)新增方法
@GetMapping("/testE") public JSONResultUtil<String> testE() { int i = 1 / 0; return JSONResultUtil.successWithData("testE"); }
(2)jmeter每秒钟十个访问
(3)curl访问:发现触发降级
$ curl http://localhost:8401/testE % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 35 0 35 0 0 140 0 --:--:-- --:--:-- --:--:-- 149Blocked by Sentinel (flow limiting)
(4)停掉jmeter再次curl,直接爆程序错误。也就是没触发sentinel熔断
$ curl http://localhost:8401/testE % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0{"timestamp":"2020-12-01T14:41:15.196+0000","status":500,"error":"Internal Server Error","message":"/ by zero","trace":"java.lang.ArithmeticException: / by zero at cn.qz.cloud.alibaba.controller.FlowLimitController.testE(FlowLimitController.java:43) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.servl
3.异常数模式
和上面的区别是根据异常数判断是否需要进行熔断。
5. 热点规则
参考:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
这个和hystrix挺像的。@SentinelResource代替@HystrixCommand。
1.新增方法
@GetMapping("/testHotKey") @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") public JSONResultUtil<String> testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2) { return JSONResultUtil.successWithData("testHotKey"); } public JSONResultUtil<String> deal_testHotKey(String p1, String p2, BlockException exception) { return JSONResultUtil.successWithData("deal_testHotKey"); //sentinel系统默认的提示:Blocked by Sentinel (flow limiting) }
解释:
@SentinelResource注解的value是便于唯一标识,在后面sentinel限流配置的时候可以使用 /testHotKey ,也可以使用 testHotKey唯一标识
blockHandler 类似于hystrixCommand注解的兜底方法。
2.sentinel增加如下配置:
上面的配置规则是:
(1)如果 testHotKey 方法不带p1 参数不进行限流
(2)如果待了p1参数且值不为"1",阈值为2;如果值为"1", 阈值可达到20。 (这里需要注意例外项的参数类型只支持8种基本数据类型和String类型)
(3)p2参数不参数限流
6. 系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
- 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 达到阈值即触发系统保护。
可以理解为系统规则是全局的限流配置,可以针对服务的全局QPS、机器的CPU等参数进行限流。
7.@SentinelResource
这个注解类似于HystrixCommand注解,可以用于方法的降级处理。
1.常规用法
package cn.qz.cloud.alibaba.controller; import cn.qz.cloud.utils.JSONResultUtil; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; 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 JSONResultUtil<String> byResource() { return JSONResultUtil.successWithData("byResource"); } public JSONResultUtil<String> handleException(BlockException exception) { return JSONResultUtil.successWithData("handleException"); } @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public JSONResultUtil<String> byUrl() { return JSONResultUtil.successWithData("byUrl"); } }
(1)新建两条限流规则:
第一条: 资源名是@SentinelResource 的value属性
第二条: 资源名是URL,等价于上面的@SentinelResource的value属性
(2)测试:
1》 byResource 超出会走自己指定的方法
2》byUrl 超出后走默认的限流方法
(3)存在的问题:
1》系统默认限流,满足不了业务要求。
2》自定义的处理方法和业务代码耦合在一起,不直观。
3》每个方法都添加一个兜底的,代码膨胀加剧。
4》全局统一的处理方法没有体现。
2. 自定义异常处理器
(1)新建异常处理器
package cn.qz.cloud.alibaba.myhandler; import cn.qz.cloud.utils.JSONResultUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; public class CustomerBlockHandler { public static JSONResultUtil<Object> handlerException(BlockException exception) { return JSONResultUtil.errorWithMsg("-1", "error"); } public static JSONResultUtil<Object> handlerException2(BlockException exception) { return JSONResultUtil.errorWithMsg("-1", "error2"); } }
(2)Controller使用@SentinelResource注解指明异常处理器以及方法
@GetMapping("/rateLimit/cusError") @SentinelResource(value = "cusError", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public JSONResultUtil<String> cusError() { return JSONResultUtil.successWithData("cusError"); }
(3)新建流控
(3) 测试超出限制
8.@SentnelResource 补充
主要属性 fallback和blockHandler。fallback管运行异常,blockHandler管配置违规,包括流量超限等。IllegalArgumentException 可以指定忽略的异常。例如:
package cn.qz.cloud.alibaba.controller; import cn.qz.cloud.bean.Payment; import cn.qz.cloud.utils.JSONResultUtil; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") //@SentinelResource(value = "fallback") //没有配置 //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常 //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规 @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) public JSONResultUtil<Payment> fallback(@PathVariable Long id) { JSONResultUtil<Payment> result = restTemplate.getForObject(SERVICE_URL + "/list/" + id, JSONResultUtil.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } //本例是fallback public JSONResultUtil<Payment> handlerFallback(@PathVariable Long id, Throwable e) { JSONResultUtil<Payment> objectJSONResultUtil = JSONResultUtil.successWithData(null); objectJSONResultUtil.setMsg("handlerFallback: " + e); return objectJSONResultUtil; } //本例是blockHandler public JSONResultUtil<Payment> blockHandler(@PathVariable Long id, BlockException blockException) { JSONResultUtil<Payment> objectJSONResultUtil = JSONResultUtil.successWithData(null); objectJSONResultUtil.setMsg("blockHandler : " + blockException); return objectJSONResultUtil; } }
nacos-payment-provider服务只有1、2、3 ID返回数据。测试:
(1)ID为4抛出非法参数异常,这种异常exceptionsToIgnore 忽略兜底
(2)ID为4以后的走fallback
9.sentinel持久化
持久化需要借助nacos,就相当于配置信息存到nacos,然后后nacos读取配置。
1.应用的yml增加如下配置:
spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: #配置Sentinel dashboard地址 dashboard: localhost:8080 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 datasource: ds1: nacos: server-addr: localhost:8848 dataId: nacos-order-consumer groupId: DEFAULT_GROUP data-type: json rule-type: flow
2.到nacos新建配置
这里需要注意,data Id 和 上面应用中配置的一样。
内容如下:
[
{
"resource": "/consumer/fallback/2",
"limitApp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
解释:
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数,1表示QPS
count:单击阈值
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm up,2表示排队等待