AlibabaCloud-sentinel熔断篇(2)
在上一篇文章中,我们介绍了使用sentinel对接口进行限流,但是Sentinel的功能当然不局限于接口拉,这个时候@SentinelResource注解就出来了,
下面就看下他的使用方法吧
第一步:首先启动sentinel控制台
java -Dserver.port=8889 -jar sentinel-dashboard-1.8.0.jar
第二步: 增加注解支持的配置:
public static void main(String[] args) {
new SpringApplicationBuilder(FebsServerTestApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
// 注解支持的配置Bean
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}
第二步: 在需要通过Sentinel来控制流量的地方使用@SentinelResource
注解,比如下面以控制Service逻辑层的某个方法为
package cc.mrbird.febs.server.test.service.impl;
import cc.mrbird.febs.server.test.service.ITestService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author: craywen
* @date: 2020-09-07 10:22
* @desc:
*/
@Service
@Slf4j
public class TestServerImpl implements ITestService {
@Override
@SentinelResource(value = "sentinelTest")
public void sentinelTest() {
log.info("----------------限流方法测试------------");
}
}
到这里就是给这个方法(简称资源点)限流了,定义了资源点之后可以实现不同的保护策略,包括:限流、降级等。
那我们先看下效果吧:首先控制台设置限流
为了演示效果,设置一秒钟2次。
postman测试效果图:
这就很明显了,直接返回的500异常,对于这种我们肯定是要坐处理的。
实现限流的异常处理
默认情况下,Sentinel对控制资源的限流处理是直接抛出异常,但是正常情况为了更好的用户业务,都会实现一些被限流之后的特殊处理,我们不希望展示一个生硬的报错。那么只需要基于上面的例子做一些加工,比如:
@Override
@SentinelResource(value = "sentinelTest",blockHandler = "exceptionHandler")
public void sentinelTest() {
log.info("----------------限流方法测试------------");
}
// 阻塞处理
private void exceptionHandler(BlockException ex){
log.error( "blockHandler:" , ex);
}
- 通过
@SentinelResource
注解的blockHandler
属性制定具体的处理函数(实现自定义异常) - 实现处理函数,该函数的传参必须与资源点的传参一样,并且最后加上
BlockException
异常参数;同时,返回类型也必须一样。
实现熔断降级
@SentinelResource
注解除了可以用来做限流控制之外,还能实现与Hystrix类似的熔断降级策略。
-
首先改造方法sentinelTest2
// 熔断与降级处理 @Override @SentinelResource(value = "sentinelTest2",fallback = "fallback") public void sentinelTest2() { log.info("sentinelTest2-------",new Date()); throw new RuntimeException("发生异常"); } public void fallback(){ log.error("fallbackHandler:" +new Date()); }
2.配置熔断策略[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
为了让效果更明显,设置熔断时间为10S,这意思就是当请求这个方法的异常比例超过50%,会触发熔断机制,最小请求可以理解成以五次请求为一个周期。
完成上面的改造之后,重启应用,并设置sentinelTest2资源的熔断降级策略(使用异常百分比),然后频繁的请求/sentinelTest2接口。在QPS>=5之后,由于这个接口一直在抛出异常,所以一定会满足熔断降级条件,这时候就会执行
fallbackHandler`方法,不断的打印如下日志:
3。为了效果我们用Apache jmeter测试:准备20个请求
先看不设置熔断的效果:
熔断效果:
话不多说,很明显吧,直接调用fallback方法。
更多注解属性说明
关于@SentinelResource
注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:
-
value
:资源名称,必需项(不能为空) -
entryType
:entry 类型,可选项(默认为EntryType.OUT
) -
blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。 -
fallback
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了
exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
-
defaultFallback
(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了
exceptionsToIgnore
里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
-
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(
DegradeException
)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException
时只会进入 blockHandler
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出。
还有啥问题呢:
因为sentinel控制台的,限流或者是熔断规则都是没有 持久性化的,所有也可以持久化到数据库中。
可以参考如下配置:
package cc.mrbird.febs.gateway.common.configure;
import cc.mrbird.febs.common.core.entity.FebsResponse;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
/**
* @author: craywen
* @date: 2020-09-04 11:10
* @desc: Sentinel 配置
* 使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
*/
@Configuration
public class FebsGatewaySentinelConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public FebsGatewaySentinelConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 异常处理模块
* @return
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 过滤器
* @return
*/
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
//初始化规则
initGatewayLimitRules();
//熔断规则
initFuseRules();
//自定义限流异常处理
initBlockHandler();
}
/**
* 熔断规则 DegradeRule
* resource 资源名,即规则的作用对象
* grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
* count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
* timeWindow 熔断时长,单位为 s
* minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
* statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
* slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
*/
private void initFuseRules(){
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("FEBS-Server-System");
// set threshold RT, 10 ms
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
/**
* 初始化规则
*/
private void initGatewayLimitRules() {
/** Set<FlowRule> rules api 限流
* resource 资源名,资源名是限流规则的作用对象
* count 限流阈值
* grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
* limitApp 流控针对的调用来源 default,代表不区分调用来源
* strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
* controlBehavior 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
* clusterMode 是否集群限流
*/
/*GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,
可以针对不同 route 或自定义的 API 分组进行限流,
支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
*/
Set<GatewayFlowRule> rules = new HashSet<>();
/*设置限流规则
resource: 资源名称,这里为路由router的ID
resourceMode: 路由模式
count: QPS即每秒钟允许的调用次数
intervalSec: 每隔多少时间统计一次汇总数据,统计时间窗口,单位是秒,默认是 1 秒。
*/
rules.add(new GatewayFlowRule("FEBS-Server-test")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
.setCount(1).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
/**
* 自定义限流异常处理
* ResultSupport 为自定义的消息封装类,代码略
*/
private void initBlockHandler() {
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange,
Throwable throwable) {
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(new FebsResponse().put("data", "系统访问量过大,限流啦!")));
}
});
}
}
系统就会自动加载配置。
参考资料:Sentinel官方文档
参考资料 http://blog.didispace.com/spring-cloud-alibaba-sentinel-2-5/