「整合系列」限流熔断
本篇主要内容是使用Sentinel给微服务加上限流熔断功能,防止异常情况拖垮应用服务。
Sentinel是面向分布式服务框架的轻量级流量控制框架,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来维护系统的稳定性。
在SpringCloud体系中,sentinel主要是为了替换原Hystrix的功能,与Hystrix相比,sentinel的隔离级别更加精细,提供的Dashboard可以在线更改限流熔断规则,而且使用也越加方便。
基础准备
要使用Sentinel提供的限流熔断能力,需要先做如下准备:
(1)安装Sentinel
Windows下安装sentinel-dashboard
(2)引入Sentinel
在需要配置限流熔断服务的POM文件中引入Sentinel组件:
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
(3)自定义资源@SentinelResource
我们只需要在相关方法上加上@SentinelResource注解,让其可以成为sentinel识别的资源即可。如:
@GetMapping("/account/{id}")
@SentinelResource("getById")
public Result getById(@PathVariable Long id) {
log.info("get account detail,id is :{}", id);
return Result.success(accountService.getById(id));
}
(3)在配置文件中添加sentinel的服务端地址
spring:
cloud:
sentinel:
transport:
# sentinel服务端地址
dashboard: 127.0.0.1:8080
# 取消延迟加载
eager: true
经过以上几步我们准备好了使用Sentinel的基础环境,接下来我们看看限流熔断的具体配置。
限流
生产者accout-service是一个核心服务,我们通过压测得出服务的最大负载能力为60。如果某个时间account-service的请求数飙升达到了600,那服务肯定就直接gg了。所以为了保护我们的accout-service,我们会给它配置一个限流规则,如果每秒钟有超过60的请求那不好意思我直接丢掉不处理了,然后丢给消费者一个异常。
总而言之,限流是通过限制调用方对自己的调用,起到保护自己系统的效果。
以下是account-service添加限流配置后,sentinel的限制界面:
限流配置
为了方便,我将accout-service的QPS单机阈值设置成5,如果每秒QPS超过5,直接丢弃。
我们查看【簇点链路】,列表展示了我们调用过的请求:
我们可以对这些请求添加流控配置,以下我们对资源名getById进行配置:
这里的资源名就是我们使用@SentinelResource注解自定义的资源。
注意:
- 不要再对@PostMapping或@GetMapping等注解指定的请求资源进行流控配置,不然会有一些奇奇怪怪的问题,我们确保只对@SentinelResource指定的资源名进行配置。
- @SentinelResource设置的资源名不要跟请求路径名一样,如上:我们的资源名就不要再配置为/account/{id}
打开浏览器,快速刷新浏览器,当每秒请求书超过5时会看到如下错误:
在后端服务日志中你会看到如下的错误日志:
2022-01-15 18:40:21.784 ERROR 16332 --- [nio-8060-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause
com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
这说明我们的目的达到了,限流成功!
自定义异常
我们可以通过@SentinelResource中添加blockHandler参数,给其添加自定义异常方法。如:
@GetMapping("/account/{id}")
@SentinelResource(value = "getById",blockHandler = "handleException")
public Result getById(@PathVariable Long id) {
log.info("get account detail,id is :{}", id);
return Result.success(accountService.getById(id));
}
/**
* 自定义异常策略
* 返回值和参数要跟目标函数一样,参数可以追加BlockException
*/
public Result handleException(Long id, BlockException exception) {
log.info("flow exception{}", exception.getClass().getCanonicalName());
return Result.error(900, "达到阈值了,不要再访问了!");
}
注意,自定义的异常方法的参数和返回值要跟目标方法一样,参数可以追加BlockException
比之前的那个错误页优雅多了!
持久化配置
由于Sentinel的配置默认是放在内存中的,每当应用重启或者sentinel重启都会丢失数据,我们这里使用Nacos作为配置中心持久化限流配置。
(1)修改pom文件,引入sentinel-datasource-nacos组件
com.alibaba.csp
sentinel-datasource-nacos
(2)修改application.yml,配置sentinel的数据源
spring:
application:
name: account-service
cloud:
sentinel:
datasource:
ds:
nacos:
server-addr: 127.0.0.1:8848
data-id: ${spring.application.name}-sentinel
group-id: DEFAULT_GROUP
rule-type: flow
(3)在nacos中建立限流配置account-service-sentinel(配置格式设置成json)
[
{
"resource": "getById",
"limitApp": "default",
"grade": 1,
"count": 3,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
可以看到上面配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:
- resource:资源名,即限流规则的作用对象
- limitApp:流控针对的调用来源,若为 default 则不区分调用来源
- grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
- count:限流阈值
- strategy:调用关系限流策略
- controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
- clusterMode:是否为集群模式
(4)进入sentinel查看dashboard,发现sentinel自动获取nacos的配置
(5)频繁刷新浏览器调用接口,验证接口是否正常限流
熔断
消费者order-service需要先调用product-service获取具体的product,然后再处理其他的业务逻辑。但是这个product-service接口不是很稳定,经常抛出异常;或者是响应缓慢,导致order-service的响应变慢;如果置之不理,order-service可能会被product-service拖垮。这时候为了保护order-service,我们需要对product-service接口进行熔断。
简而言之,熔断是通过限制自己对外部系统的调用, 起到节约响应时间、维护链路稳定的作用。
熔断配置
Sentinel中的熔断降级有三个降级策略:
-
RT(平均响应时间):
当资源的平均响应时间超过阈值之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口之内,对这个方法的调用都会自动抛出 DegradeException 异常。在下一个时间窗口到来时, 会接着再放入5个请求, 再重复上面的判断。 -
异常比例
当资源的每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口之内,对这个方法的调用都会自动地抛出DegradeException异常。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。 -
异常数
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
首先我们对原接口进行改造,让其直接抛出Runtimeexception:
@GetMapping("/product/{id}")
@SentinelResource(value = "/product/getById")
public Product getById(@PathVariable Long id) {
log.info("get product detail,id is :{}", id);
throw new RuntimeException("error");
//return productService.getById(id);
}
这里我们将product-service设置如下的熔断规则:
如果/product/getById的异常率超过50%,那么接下来2秒内直接触发熔断降级,默认情况会抛出DegradeException异常,如:
2022-01-15 22:13:54.126 ERROR 8460 --- [nio-8020-exec-8] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.reflect.UndeclaredThrowableException] with root cause
com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null
自定义异常
自定义熔断异常跟限流异常类似,我们使用fallback属性指定自定义异常的方法,如:
@GetMapping("/product/{id}")
@SentinelResource(value = "/product/getById", fallback = "fallbackHandler")
public Result getById(@PathVariable Long id) {
log.info("get product detail,id is :{}", id);
throw new RuntimeException("error");
//return Result.success(productService.getById(id));
}
/**
* 自定义熔断异常
* 返回值和参数要跟目标函数一样
*/
public Result fallbackHandler(Long id) {
return Result.error(800, "服务被熔断了,不要调用!");
}
注意,自定义的异常方法的参数和返回值要跟目标方法一样。
持久化配置
(1)引入sentinel-datasource-nacos组件,跟限流一样配置即可
(2)修改application.yml,配置sentinel的数据源
spring:
application:
name: product-service
cloud:
sentinel:
datasource:
ds:
nacos:
server-addr: 127.0.0.1:8848
data-id: ${spring.application.name}-sentinel-degrade
group-id: DEFAULT_GROUP
rule-type: degrade
(3)在nacos中建立配置文件product-service-sentinel-degrade(配置格式为JSON),做如下配置
[
{
"resource": "/product/getById",
"count": 0.5,
"grade": 1,
"minRequestAmount": 2,
"timeWindow": 2
}
]
可以看到上面配置规则是一个数组类型,数组中的每个对象是针对每一个保护资源的配置对象,每个对象中的属性解释如下:
- resource 资源名,即规则的作用对象
- grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例。0:RT 1:异常比例 2:异常数
- count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
- timeWindow 熔断时长,单位为 s
- minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入),默认是 5
- statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
- slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
(4)进入sentinel查看dashboard,发现sentinel自动获取nacos的配置