官方文档:home (sentinelguard.io)
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。是分布式系统的流量防卫兵。
Sentinel 的主要特性:
微服务搭建
jar 包启动:java -jar sentinel-dashboard-1.8.4.jar --server.port=8888
。
1 2 3 4
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| server: port: 8401 spring: application: name: sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 management: endpoints: web: exposure: include: '*'
|
1 2 3
| @SpringBootApplication @EnableDiscoveryClient public class SentinelServiceApplication {
|
流量控制
流控效果/手段:
- 快速失败:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出
FlowException
。
- 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
- Warm up 预热:根据 Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
- 该方式主要用于系统长期处于低水位的情况下,防止当流量突增时,直接把系统拉升到高水位瞬间把系统压垮的可能。
- 排队等待:匀速排队,让请求以匀速的速度通过,对应的是漏桶算法。
- 这种方式主要用于处理间隔性突发的流量,例如消息队列。在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
熔断降级
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断策略
Sentinel 提供以下几种熔断策略:
- **慢调用比例 (
SLOW_REQUEST_RATIO
)**:选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- **异常比例 (
ERROR_RATIO
)**:当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
- **异常数 (
ERROR_COUNT
)**:当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
Sentinel 熔断规则属性
属性 |
说明 |
使用范围 |
资源名 |
规则的作用对象。 |
所有熔断策略 |
熔断策略 |
Sentinel 支持3 中熔断策略:慢调用比例、异常比例、异常数策略。 |
所有熔断策略 |
最大 RT |
请求的最大相应时间,请求的响应时间大于该值则统计为慢调用。 |
慢调用比例 |
熔断时长 |
熔断开启状态持续的时间,超过该时间熔断器会切换为探测恢复状态(HALF-OPEN),单位为 s。 |
所有熔断策略 |
最小请求数 |
熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)。默认为 5。 |
所有熔断策略 |
统计时长 |
熔断触发需要统计的时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)。默认1000 ms。 |
所有熔断策略 |
比例阈值 |
分为慢调用比例阈值和异常比例阈值,即慢调用或异常调用占所有请求的百分比,取值范围 [0.0,1.0]。 |
慢调用比例 、异常比例 |
异常数 |
请求或调用发生的异常的数量。 |
异常数 |
下图为配置慢调用比例的熔断规则:
当 1s(统计时长)内持续接收 5 个(最小请求数)请求,且这些请求的响应时间超过最大 RT 的比例超过比例阈值,则在接下的 1s(熔断时长)之内,对这个方法的调用都会自动地熔断,经过熔断时间后,会进入探测恢复状态。
Sentinel 熔断状态
Sentinel 熔断降级中共涉及 3 种状态,熔断状态的之间的转换过程如下图。
Sentinel 熔断降级中共涉及 3 种状态:
- 熔断关闭状态 (CLOSED):处于关闭状态时,请求可以正常调用资源。
- 触发条件:满足以下任意条件,Sentinel 熔断器进入熔断关闭状态:全部请求访问成功。单位统计时长(statIntervalMs)内请求数目小于设置的最小请求数目。未达到熔断标准,例如服务超时比例、异常数、异常比例未达到阈值。处于探测恢复状态时,下一个请求访问成功。
- 熔断开启状态 (OPEN):处于熔断开启状态时,熔断器会一定的时间(规定的熔断时长)内,暂时切断所有请求对该资源的调用,并调用相应的降级逻辑使请求快速失败避免系统崩溃。
- 触发条件:满足以下任意条件,Sentinel 熔断器进入熔断开启状态:单位统计时长内请求数目大于设置的最小请求数目,且已达到熔断标准,例如请求超时比例、异常数、异常比例达到阈值。处于探测恢复状态时,下一个请求访问失败。
- 探测恢复状态 (HALF-OPEN):处于探测恢复状态时,Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成(没有错误)则结束熔断,熔断器进入熔断关闭(CLOSED)状态;否则会再次被熔断,熔断器进入熔断开启(OPEN)状态。
- 触发条件:在熔断开启一段时间(降级窗口时间或熔断时长,单位为 s)后,Sentinel 熔断器自动会进入探测恢复状态。
@SentinelResource 注解
@SentinelResource 注解是 Sentinel 提供的最重要的注解之一,它还包含了多个属性,如下表。
属性 |
说明 |
使用要求 |
value |
用于指定资源的名称 |
- |
blockHandler |
服务限流后会抛出 BlockException 异常,而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。 简单点说,该属性用于指定服务限流后的后续处理逻辑。 |
blockHandler 函数访问范围需要是 public;返回类型需要与原方法相匹配;参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;blockHandler 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 blockHandler 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
blockHandlerClass |
若 blockHandler 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 |
不能单独使用,必须与 blockHandler 属性配合使用;该属性指定的类中的 blockHandler 函数必须为 static 函数,否则无法解析。 |
fallback |
用于在抛出异常(包括 BlockException)时,提供 fallback 处理逻辑。 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 |
返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
fallbackClass |
若 fallback 函数与原方法不在同一个类中,则需要使用该属性指定 blockHandler 函数所在的类。 |
不能单独使用,必须与 fallback 或 defaultFallback 属性配合使用;该属性指定的类中的 fallback 函数必须为 static 函数,否则无法解析。 |
defaultFallback |
默认的 fallback 函数名称,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。 默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。 |
返回值类型必须与原函数返回值类型一致;方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常;defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
exceptionsToIgnore |
用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 |
- |
流控配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController @RequestMapping("/rateLimit") public class RateLimitController {
@GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handlerException") public CommonResult byResource() { return new CommonResult(200,"按资源名称限流测试OK", new Payment(2020L,"serial001")); } public CommonResult handlerException(BlockException e) { return new CommonResult(444, e.getClass().getCanonicalName() + "\t 服务不可用"); } }
|
当资源名配置为 /rateLimit/byResource
时,流控触发时会返回 Sentinel 自带默认的限流处理信息。当资源名配置为 byResource
时,流控触发时会调用 blockHandler
方法。
上面兜底方案面临的问题:
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加—个兜底的,那代码膨胀加剧。
- 全局统—的处理方法没有体现。
用户自定义限流处理
自定义限流处理类
1 2 3 4 5 6 7 8
| public class CustomerBlockHandler { public static CommonResult handlerException1(BlockException e) { return new CommonResult(4444,"按用户自定义 handlerException---1"); } public static CommonResult handlerException2(BlockException e) { return new CommonResult(4444,"按用户自定义 handlerException---2"); } }
|
RateLimitController
1 2 3 4 5 6
| @GetMapping("/custom") @SentinelResource(value = "custom", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException1") public CommonResult custom() { return new CommonResult(200,"按用户自定义", new Payment(2020L,"serial001")); }
|
熔断降级
服务提供者 Provider 搭建
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>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>
|
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| server: port: 8081 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: 127.0.0.1:8848 management: endpoints: web: exposure: include: '*'
|
SentinelPaymentController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RestController @RequestMapping("/sentinelPayment") public class SentinelPaymentController { @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); return new CommonResult(200,"from mysql,serverPort: " + serverPort, payment); } }
|
服务消费者 Consumer 搭建
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>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>
|
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| server: port: 81
spring: application: name: nacos-consumer-order cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: dashboard: localhost:8080 port: 8719
|
CircleBreakerController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @RestController @RequestMapping("/consumer") public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate;
@RequestMapping("/fallback/{id}") @SentinelResource(value = "fallback") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject( SERVICE_URL+"/sentinelPayment/paymentSQL/"+id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("非法参数异常...."); } else if (result.getData() == null) { throw new NullPointerException("该ID没有对应记录,空指针异常"); } return result; } }
|
只配置 fallback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @RestController @RequestMapping("/consumer") public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate;
@RequestMapping("/fallback/{id}") @SentinelResource(value = "fallback", fallback = "fallbackHandler") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject( SERVICE_URL+"/sentinelPayment/paymentSQL/"+id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException ("非法参数异常...."); } else if (result.getData() == null) { throw new NullPointerException ("该ID没有对应记录,空指针异常"); } return result; } public CommonResult fallbackHandler(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id,"null"); return new CommonResult<>( 444, "兜底异常fallbackHandler, exception内容:"+e.getMessage(), payment); } }
|
只配置 blockHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController @RequestMapping("/consumer") public class CircleBreakerController { ...... @RequestMapping("/fallback/{id}") @SentinelResource(value = "fallback", blockHandler = "blockHandler") public CommonResult<Payment> fallback(@PathVariable Long id) { ...... } public CommonResult blockHandler(@PathVariable Long id, BlockException e) { Payment payment = new Payment(id,"null"); return new CommonResult<>( 445,"blockHandler, blockException:"+e.getMessage(),payment); } }
|
sentinel 控制台配置
总结:
- fallback 管 Java 运行异常
- blockHandler 管 sentinel 控制台配置的违规异常
fallback 和 blockHandler 都配置
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑,即 blockHandler 优先级更高。
1 2 3
| @SentinelResource(value = "fallback", fallback = "fallbackHandler", blockHandler = "blockHandler") public CommonResult<Payment> fallback(@PathVariable Long id) {
|
Sentinel 服务熔断 OpenFeign
消费者配置
pom.xml
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
application.yml
1 2 3 4 5 6 7 8
| service-url: nacos-user-service: http://nacos-payment-provider
feign: sentinel: enabled: true
|
主启动类
1 2 3 4
| @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class OrderApplication {
|
feign 接口
1 2 3 4 5
| @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping(value = "/sentinelPayment/paymentSQL/{id}") CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }
|
自定义 fallback 类
1 2 3 4 5 6 7 8
| @Component public class PaymentFallbackService implements PaymentService{ @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(4444, "服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial")); } }
|
controller
1 2 3 4 5 6 7
| @Resource public PaymentService paymentService; @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { return paymentService.paymentSQL(id); }
|
测试81调用8081,此时故意关闭8081微服务提供者,81消费侧自动降级,不会被耗死。
熔断框架比较
- |
Sentinel |
Hystrix |
隔离策略 |
信号量隔离(并发线程数限流) |
线程池隔商/信号量隔离 |
熔断降级策略 |
基于响应时间、异常比率、异常数 |
基于异常比率 |
实时统计实现 |
滑动窗口(LeapArray) |
滑动窗口(基于RxJava) |
动态规则配置 |
支持多种数据源 |
支持多种数据源 |
扩展性 |
多个扩展点 |
插件的形式 |
基于注解的支持 |
支持 |
支持 |
限流 |
基于QPS,支持基于调用关系的限流 |
有限的支持 |
流量整形 |
支持预热模式匀速器模式、预热排队模式 |
不支持 |
系统自适应保护 |
支持 |
不支持 |
控制台 |
提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 |
简单的监控查看 |
持久化
1 2 3 4 5
| <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| server: port: 8401
spring: application: name: sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 datasource: ds1: nacos: server-addr: localhost:8848 dataId: sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
management: endpoints: web: exposure: include: '*'
|
添加 Nacos 业务规则配置
流量控制配置
1 2 3 4 5 6 7 8 9
| [{ "resource": "/rateLimit/byUrl", "IimitApp": "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:是否集群。
熔断配置
1 2 3 4 5 6 7 8 9
| [{ "resource": "payment-gary", "count": 1000, "timeWindow": 5, "grade": 0, "minRequestAmount": 2, "slowRatioThreshold": 0.2, "statIntervalMs": 10000 }]
|
参数名 |
描述 |
属性类型 |
枚举项 |
说明 |
resource |
资源名称 |
字符 |
—— |
资源名称,在网关应用中指scg的routes:id |
grade |
熔断策略 |
数字 |
0 |
慢调用比例,不设置此为默认值 |
|
|
|
1 |
异常比例 |
|
|
|
2 |
异常数 |
count |
值 |
数字 |
—— |
|
slowRatioThreshold |
比例阈值 |
数字 |
—— |
熔断策略是慢调用时存在,单位是0.0-1.0的小数 |
timeWindow |
熔断时长 |
数字 |
—— |
熔断规则所要熔断全部请求的时间范围,单位是秒 |
minRequestAmount |
最小请求数 |
数字 |
—— |
熔断时必须达到的最小请求数量 |
statIntervalMs |
统计时长 |
数字 |
—— |
熔断规则所统计的时间范围,单位毫秒 |
count
参数说明:当熔断策略是慢调用时,此值为最大 RT,单位是毫秒;当熔断是策略是异常比例时,此值为异常比例,单位是 0.0-1.0 之间的小数;当熔断策略是异常数时,此值为异常数目。