1. 序言 由Java的示例,其实我们在spring中也可以根据sentinel-core
通过切面AOP来自定义实现一个拦截,这很容易,Sentinel自己也实现了Spring的使用
Sentinel自行实现了两套拦截的方案:
一套是基于Filter的拦截,通过映射请求路径的方式,代码中在config中配置好路径对应的资源策略
另一套是基于Interceptor方案,通过添加@SentinelResource
,拦截对应的数据
2. 工程搭建 2.1. 基于Servlet的Filter 2.1.1. pom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-core</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-web-servlet</artifactId > </dependency > </dependencies >
2.1.2. 应用配置 启动器按照springBoot正常启动即可
配置如下:
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 40 41 42 43 @Configuration public class SentinelConfig implements InitializingBean { @Bean public FilterRegistrationBean sentinelFilterRegistration () { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean <>(); CommonFilter filter = new CommonFilter (); registration.setFilter(filter); registration.addUrlPatterns("/*" ); registration.setName("sentinelFilter" ); return registration; } @Override public void afterPropertiesSet () throws Exception { FlowRule flowRule = new FlowRule (); flowRule.setResource("/hello" ); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(1 ); flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); FlowRuleManager.loadRules(Arrays.asList(flowRule)); } }
2.1.3. 应用中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController public class HelloController { @GetMapping("/hello") public Object hello () { System.out.println("线程:" +Thread.currentThread().getName()+ "执行" ); doBusiness(); return "hello" ; } private void doBusiness () { try { TimeUnit.MILLISECONDS.sleep(5000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } }
当我们访问/hello
接口时便会加载CommonFilter过滤器执行方法
2.1.4. 代码分析 上面我们配置了过滤器,现在主要看过滤器CommonFilter
中的方法
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 40 41 42 43 44 45 46 47 48 49 @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest sRequest = (HttpServletRequest) request; Entry urlEntry = null ; try { String target = FilterUtil.filterTarget(sRequest); UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner(); if (urlCleaner != null ) { target = urlCleaner.clean(target); } if (!StringUtil.isEmpty(target)) { String origin = parseOrigin(sRequest); String contextName = webContextUnify ? WebServletConfig.WEB_SERVLET_CONTEXT_NAME : target; ContextUtil.enter(contextName, origin); if (httpMethodSpecify) { String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target; urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN); } else { urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN); } } chain.doFilter(request, response); } catch (BlockException e) { HttpServletResponse sResponse = (HttpServletResponse) response; WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e); } catch (IOException | ServletException | RuntimeException e2) { Tracer.traceEntry(e2, urlEntry); throw e2; } finally { if (urlEntry != null ) { urlEntry.exit(); } ContextUtil.exit(); } }
从这里代码可以看到这里也是统一封装了sentinel-core的通用入口。
2.2. 基于SpringMvc拦截器实现 2.2.1. pom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-core</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-spring-webmvc-adapter</artifactId > </dependency > </dependencies >
2.2.2. 配置 springboot 启动类不变,这里主要配置拦截器信息
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 @Component public class SentinelConfig implements WebMvcConfigurer , InitializingBean { @Override public void addInterceptors (InterceptorRegistry registry) { SentinelWebMvcConfig webMvcConfig = new SentinelWebMvcConfig (); registry.addInterceptor(new SentinelWebInterceptor (webMvcConfig)).addPathPatterns("/**" ); } @Override public void afterPropertiesSet () throws Exception { FlowRule flowRule = new FlowRule (); flowRule.setResource("/hello" ); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(1 ); flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); FlowRuleManager.loadRules(Arrays.asList(flowRule)); } }
更多使用见 官方文档
2.2.3. 应用中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController public class HelloController { @GetMapping("/hello") public Object hello () { System.out.println("线程:" +Thread.currentThread().getName()+ "执行" ); doBusiness(); return "hello" ; } private void doBusiness () { try { TimeUnit.MILLISECONDS.sleep(5000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } }
2.2.4. 代码分析 上面我们配置了过滤器,现在主要看拦截器SentinelWebInterceptor
中的preHandle
方法
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 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String resourceName = getResourceName(request); if (StringUtil.isEmpty(resourceName)) { return true ; } if (increaseReferece(request, this .baseWebMvcConfig.getRequestRefName(), 1 ) != 1 ) { return true ; } String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin); 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 ; } }
2.3. spring切面拦截方式 2.3.1. pom 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-core</artifactId > </dependency > <dependency > <groupId > com.alibaba.csp</groupId > <artifactId > sentinel-annotation-aspectj</artifactId > </dependency > </dependencies >
2.3.2. 配置 springboot 普通启动,配置类:
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 @Configuration public class SentinelConfig implements InitializingBean { @Bean SentinelResourceAspect sentinelResourceAspect () { return new SentinelResourceAspect (); } @Override public void afterPropertiesSet () throws Exception { FlowRule flowRule = new FlowRule (); flowRule.setResource("hello" ); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(1 ); flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); FlowRuleManager.loadRules(Arrays.asList(flowRule)); } }
这里需要注入切面管理的类,
2.3.3. 应用中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RestController public class HelloController { @GetMapping("/hello") @SentinelResource("hello") public Object hello () { System.out.println("线程:" +Thread.currentThread().getName()+ "执行" ); doBusiness(); return "hello" ; } private void doBusiness () { try { TimeUnit.MILLISECONDS.sleep(5000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } }
2.3.4. 代码分析 spring aop的方式我们主要看注入的切面类SentinelResourceAspect
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 40 41 42 43 44 45 46 @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 ) { 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(); if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); } throw ex; } finally { if (entry != null ) { entry.exit(1 , pjp.getArgs()); } } } }
这种切面的方式也好理解,使用了我们通用的注解方式匹配的。
3. 总结 以上三种方式其实原理是一样的,核心都是调用到sentinel-core
中的SphU.entry()
,entry.exit()
方法开启关闭流控