文章目录
Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。是分布式系统的流量防卫兵。
Sentinel 的主要特性:
微服务搭建
jar 包启动:java -jar sentinel-dashboard-1.8.4.jar --server.port=8888
。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
port: 8401
spring:
application:
name: sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #sentinel dashboard地址
#指定应用与sentinel控制台交互的端口,需要另启一个HttpServer【sentinel后台监控服务】
port: 8719
management: #图形化监控
endpoints:
web:
exposure:
include: '*'
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelServiceApplication {
流量控制
-
资源名:唯一名称,默认请求路径。
-
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。
-
阈值类型/单机阈值:
- QPS(每秒钟的请求数量)︰当调用该API的QPS达到阈值的时候,进行限流。
- 线程数:当调用该API的线程数达到阈值的时候,进行限流。
-
是否集群:不需要集群。
-
流控模式:
- 直接:API达到限流条件时,直接限流。
- 关联:当关联的资源达到阈值时,就限流自己。
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】。
-
流控效果/手段:
- 快速失败:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出
FlowException
。- 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
- Warm up 预热:根据 Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
- 该方式主要用于系统长期处于低水位的情况下,防止当流量突增时,直接把系统拉升到高水位瞬间把系统压垮的可能。
- 排队等待:匀速排队,让请求以匀速的速度通过,对应的是漏桶算法。
- 这种方式主要用于处理间隔性突发的流量,例如消息队列。在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
- 快速失败:默认的流量控制方式,当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 逻辑中,而是会原样抛出。 | - |
流控配置
@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
方法。
上面兜底方案面临的问题:
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加—个兜底的,那代码膨胀加剧。
- 全局统—的处理方法没有体现。
用户自定义限流处理
自定义限流处理类
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
@GetMapping("/custom")
@SentinelResource(value = "custom", blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException1")
public CommonResult custom() {
return new CommonResult(200,"按用户自定义", new Payment(2020L,"serial001"));
}
熔断降级
服务提供者 Provider 搭建
pom.xml
<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
server:
port: 8081
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
SentinelPaymentController
@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
<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
server:
port: 81
spring:
application:
name: nacos-consumer-order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #注册进Nacos
sentinel:
transport:
dashboard: localhost:8080
port: 8719
CircleBreakerController
@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
@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
@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 优先级更高。
@SentinelResource(value = "fallback", fallback = "fallbackHandler",
blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
Sentinel 服务熔断 OpenFeign
消费者配置
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
application.yml
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
主启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
feign 接口
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/sentinelPayment/paymentSQL/{id}")
CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
自定义 fallback 类
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(4444,
"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
controller
//========OpenFeign=========
@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,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持预热模式匀速器模式、预热排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 | 简单的监控查看 |
持久化
<!--sentinel持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
server:
port: 8401
spring:
application:
name: sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #sentinel dashboard地址
port: 8719
datasource: #添加Nacos数据源配置,sentinel持久化
ds1:
nacos:
server-addr: localhost:8848
dataId: sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management: #图形化监控
endpoints:
web:
exposure:
include: '*'
添加 Nacos 业务规则配置
流量控制配置
[{
"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:是否集群。
熔断配置
[{
"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 之间的小数;当熔断策略是异常数时,此值为异常数目。