Sentinel注解支持
官方文档:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
这一节,我们首先做一个小的案例,然后把官方文档中的介绍过一遍,再把文档所述的特性在代码中找到。
案例 依赖 1 2 3 4 5 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > <version > 2.1.0.RELEASE</version > </dependency >
属性文件 1 2 3 4 5 6 7 8 9 10 spring: cloud: sentinel: transport: dashboard: 127.0 .0 .1 :8080 port: 8719 application: name: sentinel-annotaion server: port: 9999
说明 :
spring.application.name
定义应用名,如图所示的名称
server.port
:应用端口
spring.cloud.sentinel.transport.dashboard
:sentinel的IP:端口
spring.cloud.sentinel.transport.port
:sentinel 与服务的通讯端口
切面 如果使用的是Spring Boot/Cloud ,即没有导入前面的依赖,需要自己将切面纳入到Spring容器中去:
1 2 3 4 @Bean public SentinelResourceAspect sentinelResourceAspect () { return new SentinelResourceAspect(); }
由于我们导入了springcloud的依赖,所以会自动配置好这个切面,源码如下:
EchoService 1 2 3 4 public interface EchoService { String echoMessage (String message) ; String hello () ; }
EchoServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Service public class EchoServiceImpl implements EchoService { @Override @SentinelResource (value = "echo.message" ,blockHandler = "handleException" , blockHandlerClass = ExceptionUtil.class ) public String echoMessage (String message ) { return "echo message:" +message; } @Override @SentinelResource (value = "hello" ,blockHandler = "handleHello" ) public String hello () { return "echo hello" ; } public String handleHello (BlockException ex) { return "handle hello ; exception:" +ex; } }
ExceptionUtil 1 2 3 4 5 public class ExceptionUtil { public static String handleException (String message, BlockException ex) { return "exception handle " +message + " exception:" +ex; } }
EchoController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController public class EchoController { @Autowired private EchoService echoService; @GetMapping ("/echo/message/{message}" ) public String echoMessage (@PathVariable String message) { return echoService.echoMessage(message); } @GetMapping ("/hello" ) public String hello () { return echoService.hello(); } }
页面控制流控 流控限制如下,简单使用基于QPS
的流控规则(规则后续会详细说到),阈值为2
,流控效果选的是快速失败
。
通过测试,当我们在页面快速刷新(达到一秒访问3次或3次以上)可以看到下面的效果。
@SentinelResource 注解 @sentinelResource
注解用于定义资源,并提供可选的异常处理和fallback配置项,@sentinelResource
注解包含以下属性:
注解源码 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 @Target (ElementType.METHOD)@Retention (RetentionPolicy.RUNTIME)@Inherited public @interface SentinelResource { String value () default "" ; EntryType entryType () default EntryType.OUT ; String blockHandler () default "" ; Class<?>[] blockHandlerClass() default {}; String fallback () default "" ; String defaultFallback () default "" ; Class<?>[] fallbackClass() default {}; Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class } ; Class<? extends Throwable>[] exceptionsToIgnore() default {}; }
文档原文 @SentinelResource
用于定义资源,并提供可选的异常处理和 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
直接抛出
@SentinelResource逻辑分析 源码分析 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 @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(); Entry entry = null ; try { entry = SphU.entry(resourceName, entryType, 1 , 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()); } } }
我们主要看 catch 中的handleBlockException()
方法。
第一步:执行 blockHandler所配置的方法
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 protected Object handleBlockException (ProceedingJoinPoint pjp, SentinelResource annotation, BlockException ex) throws Throwable { Method blockHandlerMethod = extractBlockHandlerMethod(pjp, annotation.blockHandler(), annotation.blockHandlerClass()); if (blockHandlerMethod != null ) { Object[] originArgs = pjp.getArgs(); Object[] args = Arrays.copyOf(originArgs, originArgs.length + 1 ); args[args.length - 1 ] = ex; try { if (isStatic(blockHandlerMethod)) { return blockHandlerMethod.invoke(null , args); } return blockHandlerMethod.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } return handleFallback(pjp, annotation, ex); } private Method extractBlockHandlerMethod (ProceedingJoinPoint pjp, String name, Class<?>[] locationClass) { if (StringUtil.isBlank(name)) { return null ; } boolean mustStatic = locationClass != null && locationClass.length >= 1 ; Class<?> clazz; if (mustStatic) { clazz = locationClass[0 ]; } else { clazz = pjp.getTarget().getClass(); } MethodWrapper m = ResourceMetadataRegistry.lookupBlockHandler(clazz, name); if (m == null ) { Method method = resolveBlockHandlerInternal(pjp, name, clazz, mustStatic); ResourceMetadataRegistry.updateBlockHandlerFor(clazz, name, method); return method; } if (!m.isPresent()) { return null ; } return m.getMethod(); } private Method resolveBlockHandlerInternal (ProceedingJoinPoint pjp, String name, Class<?> clazz,boolean mustStatic) { Method originMethod = resolveMethod(pjp); Class<?>[] originList = originMethod.getParameterTypes(); Class<?>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1 ); parameterTypes[parameterTypes.length - 1 ] = BlockException.class ; return findMethod(mustStatic, clazz, name, originMethod.getReturnType(), parameterTypes); } private Method findMethod (boolean mustStatic, Class<?> clazz, String name, Class<?> returnType, Class<?>... parameterTypes) { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if (name.equals(method.getName()) && checkStatic(mustStatic, method) && returnType.isAssignableFrom(method.getReturnType()) && Arrays.equals(parameterTypes, method.getParameterTypes())) { RecordLog.info("Resolved method [{0}] in class [{1}]" , name, clazz.getCanonicalName()); return method; } } Class<?> superClass = clazz.getSuperclass(); if (superClass != null && !Object.class .equals (superClass )) { return findMethod(mustStatic, superClass, name, returnType, parameterTypes); } else { String methodType = mustStatic ? " static" : "" ; RecordLog.warn("Cannot find{0} method [{1}] in class [{2}] with parameters {3}" , methodType, name, clazz.getCanonicalName(), Arrays.toString(parameterTypes)); return null ; } }
第二步:执行fallback所配置的方法,进入handleFallback()
方法
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 protected Object handleFallback (ProceedingJoinPoint pjp, SentinelResource annotation, Throwable ex) throws Throwable { return handleFallback(pjp, annotation.fallback(), annotation.defaultFallback(), annotation.fallbackClass(), ex); } protected Object handleFallback (ProceedingJoinPoint pjp, String fallback, String defaultFallback, Class<?>[] fallbackClass, Throwable ex) throws Throwable { Object[] originArgs = pjp.getArgs(); Method fallbackMethod = extractFallbackMethod(pjp, fallback, fallbackClass); if (fallbackMethod != null ) { int paramCount = fallbackMethod.getParameterTypes().length; Object[] args; if (paramCount == originArgs.length) { args = originArgs; } else { args = Arrays.copyOf(originArgs, originArgs.length + 1 ); args[args.length - 1 ] = ex; } try { if (isStatic(fallbackMethod)) { return fallbackMethod.invoke(null , args); } return fallbackMethod.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } return handleDefaultFallback(pjp, defaultFallback, fallbackClass, ex); } private Method extractFallbackMethod (ProceedingJoinPoint pjp, String fallbackName, Class<?>[] locationClass) { if (StringUtil.isBlank(fallbackName)) { return null ; } boolean mustStatic = locationClass != null && locationClass.length >= 1 ; Class<?> clazz = mustStatic ? locationClass[0 ] : pjp.getTarget().getClass(); MethodWrapper m = ResourceMetadataRegistry.lookupFallback(clazz, fallbackName); if (m == null ) { Method method = resolveFallbackInternal(pjp, fallbackName, clazz, mustStatic); ResourceMetadataRegistry.updateFallbackFor(clazz, fallbackName, method); return method; } if (!m.isPresent()) { return null ; } return m.getMethod(); } private Method resolveFallbackInternal (ProceedingJoinPoint pjp, String name, Class<?> clazz,boolean mustStatic) { Method originMethod = resolveMethod(pjp); Class<?>[] defaultParamTypes = originMethod.getParameterTypes(); Class<?>[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1 ); paramTypesWithException[paramTypesWithException.length - 1 ] = Throwable.class ; Method method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), defaultParamTypes); if (method == null ) { method = findMethod(mustStatic, clazz, name, originMethod.getReturnType(), paramTypesWithException); } return method; }
第三步:执行defaultFallback属性配置的函数,进入handleDefaultFallback()
方法
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 protected Object handleDefaultFallback (ProceedingJoinPoint pjp, String defaultFallback, Class<?>[] fallbackClass, Throwable ex) throws Throwable { Method fallbackMethod = extractDefaultFallbackMethod(pjp, defaultFallback, fallbackClass); if (fallbackMethod != null ) { Object[] args = fallbackMethod.getParameterTypes().length == 0 ? new Object[0 ] : new Object[] {ex}; try { if (isStatic(fallbackMethod)) { return fallbackMethod.invoke(null , args); } return fallbackMethod.invoke(pjp.getTarget(), args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } throw ex; } private Method extractDefaultFallbackMethod (ProceedingJoinPoint pjp, String defaultFallback, Class<?>[] locationClass) { if (StringUtil.isBlank(defaultFallback)) { return null ; } boolean mustStatic = locationClass != null && locationClass.length >= 1 ; Class<?> clazz = mustStatic ? locationClass[0 ] : pjp.getTarget().getClass(); MethodWrapper m = ResourceMetadataRegistry.lookupDefaultFallback(clazz, defaultFallback); if (m == null ) { Class<?> originReturnType = resolveMethod(pjp).getReturnType(); Class<?>[] defaultParamTypes = new Class<?>[0 ]; Class<?>[] paramTypeWithException = new Class<?>[] {Throwable.class } ; Method method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, defaultParamTypes); if (method == null ) { method = findMethod(mustStatic, clazz, defaultFallback, originReturnType, paramTypeWithException); } ResourceMetadataRegistry.updateDefaultFallbackFor(clazz, defaultFallback, method); return method; } if (!m.isPresent()) { return null ; } return m.getMethod(); }
如果把上面的代码过一遍的话,前面文档中总结的要点基本都理解了。
执行方法顺序: blockHandler –> fallback –> defaultFallback
处理BlockException 属性方法形参:
blockHandler:在原方法的形参列表基础上,需要在最后添加一个BlockException
类型的参数。
1 2 3 4 5 6 Class<?>[] originList = originMethod.getParameterTypes(); Class<?>[] parameterTypes = Arrays.copyOf(originList, originList.length + 1 ); parameterTypes[parameterTypes.length - 1 ] = BlockException.class ;
fallback:有两种:1:方法参数列表与原方法参数列表一致,2:在原方法列表后添加一个Throwable
类型的参数
1 2 3 4 5 6 Class<?>[] defaultParamTypes = originMethod.getParameterTypes(); Class<?>[] paramTypesWithException = Arrays.copyOf(defaultParamTypes, defaultParamTypes.length + 1 ); paramTypesWithException[paramTypesWithException.length - 1 ] = Throwable.class ;
defaultFallback:有两种,1:方法参数列表为空,2:仅有一个Thowable
的参数列表
1 2 3 4 5 6 7 8 Class<?>[] defaultParamTypes = new Class<?>[0 ]; Class<?>[] paramTypeWithException = new Class<?>[] {Throwable.class } ;
处理非BlockException 如果配置了在忽略异常列表中,则直接抛出原始异常,否则使用exceptionToTrace
配置进行处理,如果没有配置,则直接抛出原始异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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;
annotation.exceptionsToTrace()
默认是Throwable.class
类型的,所以,理论上fallback是可以处理任何异常,排序在忽略异常列表中的异常。
这一节通过阅读源码的方式来学习sentinel的注解支持,后面我们看一下sentinel的几种控制规则。