springcloud 整合sentinel
一、参考官网:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
1. 搭建sentinel Dashborad
1. 下载jar包: Releases · alibaba/Sentinel (github.com)
2. 启动:java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
3. 如果8080端口冲突,就更改端口
2. springcloud项目接入:
1.引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2.在application.yml中配置:
spring: cloud: sentinel: transport: port: 8719 dashboard: localhost:8080
随便访问Controller:
点击流控:配置流控规则:每秒只允许一个请求
请求超过限制报错:
3. 自定义报错:
/** * author: yangxiaohui * date: 2023/8/7 */ @Component public class SentinelBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { httpServletResponse.setCharacterEncoding("utf-8"); JSONObject jsonObject = new JSONObject(); jsonObject.put("ErrorCode","500"); jsonObject.put("Msg","To Many Request"); httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject)); } }
源码分析:sentinel为何能够对http请求进行限流以及为何能自定义返回对象:(省略部分代码)
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin);
//sentinel 限流核心代码,在这里不分析 如果限流不通过就会抛异常BlockException Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
return true; } catch (BlockException e) { try { handleBlockException(request, response, e); //限流失败的返回值 } finally { ContextUtil.exit(); } return false; } } protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { //限流失败最终是交给 BaseWebMvcConfig的一个接口处理 baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { // Throw BlockException directly. Users need to handle it in Spring global exception handler. throw e; } } }
通过源码分析,如果了解过springMvc源码可以知道,要拦截请求只要定义一个拦截器HandlerInterceptor 即可,在拦截的方法里,根据请求路径,校验请求有没达到限流的要求,达到就抛异常,捕捉到异常后,在异常里面处理响应逻辑:
之后我们再看看SentinelWebAutoConfiguration这个配置类:
@Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnProperty( name = {"spring.cloud.sentinel.enabled"}, matchIfMissing = true ) @ConditionalOnClass({SentinelWebInterceptor.class}) @EnableConfigurationProperties({SentinelProperties.class}) public class SentinelWebAutoConfiguration implements WebMvcConfigurer { private static final Logger log = LoggerFactory.getLogger(SentinelWebAutoConfiguration.class); @Autowired private SentinelProperties properties; @Autowired private Optional<UrlCleaner> urlCleanerOptional; @Autowired private Optional<BlockExceptionHandler> blockExceptionHandlerOptional; @Autowired private Optional<RequestOriginParser> requestOriginParserOptional; @Autowired private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional; public SentinelWebAutoConfiguration() { } //将拦截器也就是SentinelWebInterceptor(继承了AbstractSentinelInterceptor)注册到Springmvc中 public void addInterceptors(InterceptorRegistry registry) { if (this.sentinelWebInterceptorOptional.isPresent()) { Filter filterConfig = this.properties.getFilter(); registry.addInterceptor((HandlerInterceptor)this.sentinelWebInterceptorOptional.get()).order(filterConfig.getOrder()).addPathPatterns(filterConfig.getUrlPatterns()); log.info("[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.", filterConfig.getUrlPatterns()); } } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//拦截器 public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) { return new SentinelWebInterceptor(sentinelWebMvcConfig); } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//SentinelWebMvcConfig 继承了BaseWebMvcConfig public SentinelWebMvcConfig sentinelWebMvcConfig() { SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig(); sentinelWebMvcConfig.setHttpMethodSpecify(this.properties.getHttpMethodSpecify()); sentinelWebMvcConfig.setWebContextUnify(this.properties.getWebContextUnify()); if (this.blockExceptionHandlerOptional.isPresent()) { //如果容器中提供了限流异常处理器,就用提供的 this.blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler); } else if (StringUtils.hasText(this.properties.getBlockPage())) { //如果提供了限流异常跳转页面 sentinelWebMvcConfig.setBlockExceptionHandler((request, response, e) -> { response.sendRedirect(this.properties.getBlockPage()); }); } else { //都没提供就用默认的 sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); } this.urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner); this.requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser); return sentinelWebMvcConfig; } }
通过代码分析,如果没有提供异常处理器,就会有个默认的异常处理,提供了,就用提供的,我们看看默认的 DefaultBlockExceptionHandler
所以,我们自定义的异常处理器可以替换默认的异常处理器
4. 前面分析,sentinel整合springcloud默认会对http请求进行拦截,本质是springmvc的拦截器导致的,那么如何对feign进行限流配置呢?
1. 在application.yaml中开启配置:
feign:
sentinel:
enabled: true
2. 简单demo示例
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) String echo(@PathVariable("str") String str); } class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } } class EchoServiceFallback implements EchoService { @Override public String echo(@PathVariable("str") String str) { return "echo fallback"; } }
5.如果我们想拦截非http请求的方法:基于 @SentinelResource注解实现拦截
/** * author: yangxiaohui * date: 2023/8/7 */ @Service public class TestService { @SentinelResource(value = "sayHello",blockHandler = "blockHandler",blockHandlerClass ={BlockClass.class} ) public String sayHello(String name) { return "Hello, " + name; } public static class BlockClass{ //这个方法必须是静态方法 public static String blockHandler(String name, BlockException blockException){ System.out.println("被阻塞了。。。。。。。。。。。。"); return "haha"; } } }
实现原理是AOP:
@Aspect public class SentinelResourceAspect extends AbstractSentinelAspectSupport { @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable { Method originMethod = resolveMethod(pjp); SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class); if (annotation == null) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } String resourceName = getResourceName(annotation.value(), originMethod); EntryType entryType = annotation.entryType(); int resourceType = annotation.resourceType(); Entry entry = null; try { entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs()); Object result = pjp.proceed(); return result; } catch (BlockException ex) { return handleBlockException(pjp, annotation, ex); } catch (Throwable ex) { Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); } // No fallback function can handle the exception, so throw it out. throw ex; } finally { if (entry != null) { entry.exit(1, pjp.getArgs()); } } } }
6. gateWay网关配置:
1.引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
2. 配置yml
spring: cloud: sentinel: filter: enabled: false transport: port: 8719 dashboard: localhost:8080
调用后:
3. 限流失败后,返回默认错误
/** * author: yangxiaohui * date: 2023/8/7 */ @Configuration public class SentinelGatewayConfig { public SentinelGatewayConfig() { GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { JSONObject jsonObject = new JSONObject(); jsonObject.put("ErrorCode","500"); jsonObject.put("Msg","To Many Request"); return ServerResponse.ok().body(Mono.just(JSON.toJSONString(jsonObject)), String.class); } }); } }
7.前面各种框架的整合,核心代码,其实还是:
try (Entry entry = SphU.entry("HelloWorld")) { // Your business logic here. System.out.println("hello world"); } catch (BlockException e) { // Handle rejected request. e.printStackTrace(); }
源码大概:
用官网的一张原理图展示:
具体源码过程
8.持久化流控规则 使用nacos
1.引入依赖 在自己项目中引入
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
2. 在yaml中配置:这里以流控规则Flow为例,其他的规则如热点规则等,只要改下名称、dataId 、rule-type
spring: cloud: sentinel: transport: dashboard: localhost:8080 datasource: # 名称随意 flow: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-flow groupId: DEFAULT_GROUP rule-type: flow
对应nacos的值:
规则是一个数组,可以配置多个:
Flow规则:
[ { // 资源名 "resource": "/test", // 针对来源,若为 default 则不区分调用来源 "limitApp": "default", // 限流阈值类型(1:QPS;0:并发线程数) "grade": 1, // 阈值 "count": 1, // 是否是集群模式 "clusterMode": false, // 流控效果(0:快速失败;1:Warm Up(预热模式);2:排队等待) "controlBehavior": 0, // 流控模式(0:直接;1:关联;2:链路) "strategy": 0, // 预热时间(秒,预热模式需要此参数) "warmUpPeriodSec": 10, // 超时时间(排队等待模式需要此参数) "maxQueueingTimeMs": 500, // 关联资源、入口资源(关联、链路模式) "refResource": "ddd" } ]
降级规则:
[ { // 资源名 "resource": "/test1", "limitApp": "default", // 熔断策略(0:慢调用比例,1:异常比率,2:异常计数) "grade": 0, // 最大RT、比例阈值、异常数 "count": 200, // 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) "slowRatioThreshold": 0.2, // 最小请求数 "minRequestAmount": 5, // 当单位统计时长(类中默认1000) "statIntervalMs": 1000, // 熔断时长 "timeWindow": 10 } ]
热点规则:
[ { // 资源名 "resource": "/test1", // 限流模式(QPS 模式,不可更改) "grade": 1, // 参数索引 "paramIdx": 0, // 单机阈值 "count": 13, // 统计窗口时长 "durationInSec": 6, // 是否集群 默认false "clusterMode": 默认false, // "burstCount": 0, // 集群模式配置 "clusterConfig": { // "fallbackToLocalWhenFail": true, // "flowId": 2, // "sampleCount": 10, // "thresholdType": 0, // "windowIntervalMs": 1000 }, // 流控效果(支持快速失败和匀速排队模式) "controlBehavior": 0, // "limitApp": "default", // "maxQueueingTimeMs": 0, // 高级选项 "paramFlowItemList": [ { // 参数类型 "classType": "int", // 限流阈值 "count": 222, // 参数值 "object": "2" } ] } ]
系统规则:
[ { // RT "avgRt": 1, // CPU 使用率 "highestCpuUsage": -1, // LOAD "highestSystemLoad": -1, // 线程数 "maxThread": -1, // 入口 QPS "qps": -1 } ]
授权规则:
[ { // 资源名 "resource": "sentinel_spring_web_context", // 流控应用 "limitApp": "/test", // 授权类型(0代表白名单;1代表黑名单。) "strategy": 0 } ]
注意:配置时,去掉上面的注释,保证json格式正确
配置多个规则时,在yml中:
spring: cloud: sentinel: transport: dashboard: localhost:8080 datasource: # 名称随意 flow: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-flow groupId: DEFAULT_GROUP rule-type: flow #系统规则 system: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-system groupId: DEFAULT_GROUP rule-type: system
rule-type 配置表示该数据源中的规则属于哪种类型的规则(flow, degrade, authority, system, param-flow, gw-flow, gw-api-group)。注意网关流控规则 (GatewayFlowRule) 对应 gw-flow。