sentinel中blockHandler、blockHandlerClass、fallback、defaultFallback、fallbackClas实现原理、配置说明

我们知道现在项目一般都会使用springboot作为基础框架,如果项目需要基于springboot实现自动导入的话,一般都会实现一个xxx-starter,我们看看spring-cloud-starter-alibaba-sentinel是怎么实现的。
本篇有些内容需要基于之前一篇的分析Sentinel限流原理(基于Sentinel1.8.1),限流、熔断、热点参数限流、授权实现原理

springboot自动装配原理,底层源码分析,条件注解实现机制,EnableAutoConfiguration,AutoConfigurationImportSelector

我们看下spring-cloud-starter-alibaba-sentinelspring.factoryies内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.sentinel.SentinelWebAutoConfiguration,\
com.alibaba.cloud.sentinel.SentinelWebFluxAutoConfiguration,\
com.alibaba.cloud.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration,\
com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration

org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker=\
com.alibaba.cloud.sentinel.custom.SentinelCircuitBreakerConfiguration

其中比较重要的一个是SentinelAutoConfiguration在这个自动注入配置里面,会注入SentinelResourceAspect这是springboot中实现基于SentinelResource流控的一个关键。

@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();
            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());
            }
        }
    }
}

可以看到SentinelResourceAspect上有Aspect注解,基于spring的AOP功能,在有SentinelResource的方法上会进行切面处理。
这里在执行实际方法之前,会通过SphU.entry(resourceName, resourceType, entryType, pjp.getArgs())去获取entry,如果获取到就执行方法,获取不到,按照之前的分析,会抛出BlockException型异常。
而对于资源名称的获取,首先是直接获取SentinelResource注解有没有值,如果有值则用该值作为资源名称,否则的话,资源名称为:类全名称+:+方法名称+(+参数类型列表+)
EntryType默认为EntryType.OUT

BlockException异常处理

如果访问出现了BlockException,那么会调用handleBlockException,调用blockHandler或者blockHandlerClass进行处理。

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();
            // Construct args.
            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);
    }

对于是否应该调用哪个方法,Sentinel中是这样判断的:
首先必须blockHandler必须有值,如果没配置的化,那么不执行block处理,如果配置blockHandler也配置了blockHandlerClass,那么会从blockHandlerClass数组的第一个元素作为待执行的类且待执行的block方法必须是静态方法方法名与blockHandler配置一致;如果配置blockHandler但是没有配置blockHandlerClass,那么取当前执行方法的类为待执行类,block方法名与blockHandler配置一致。如果待执行类不是当前类,那么方法必须是一个静态方法,且方法名与blockHandler配置一致;如果待执行类的是当前类,那么方法静态或非静态方法都可以;而方法的返回值必须与SentinelResource注解的方法返回值一样,参数方面,必须比SentinelResource注解的方法的参数多一个,最后一个参数必须是BlockException类型,前面的参数必须与SentinelResource注解的方法参数完全一致。
比如有如下方法:

@SentinelResource(blockHandler="handleBolckForTest")
public String test(String name,int age){
	xxxxx
}

如果是当前类处理,那么该方法与上述方法必须在一个类,且方法定义如下:

public String handleBolckForTest(String name,int age,BlockException exception){
	xxxxx
}

如果是放在其他类中处理,那么如下:

// Test.java
@SentinelResource(blockHandler="handleBolckForTest",blockHandlerClas={BlockHandlerClassTest})
public String test(String name,int age){
	xxxxx
}
// BlockHandlerClassTest.java
public static String handleBolckForTest(String name,int age,BlockException exception){
	xxxxx
}

如果说没有配置任何blockHandler处理逻辑,那么将走handleFallback逻辑。

非BlockException异常处理

如果方法执行过程中抛出了非BlockException异常,

  • 如果配置exceptionsToIgnore属性,那么判断抛出的异常是否是该类型异常,如果是,表明不用处理该类型异常,直接对外抛出该异常
  • 如果配置了exceptionsToTrace,那么判断抛出的异常是否是该类型异常,如果是的话且需要被trace,那么会调用entry.setError,默认exceptionsToTrace=T{Throwable.class},而这里哪些类需要被Trace则是由Tracer去判断:
protected static boolean shouldTrace(Throwable t) {
        if (t == null || t instanceof BlockException) {
            return false;
        }
        if (exceptionPredicate != null) {
            return exceptionPredicate.test(t);
        }

        if (ignoreClasses != null) {
            for (Class<? extends Throwable> clazz : ignoreClasses) {
                if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
                    return false;
                }
            }
        }
        if (traceClasses != null) {
            for (Class<? extends Throwable> clazz : traceClasses) {
                if (clazz != null && clazz.isAssignableFrom(t.getClass())) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }

如果有需要,大家可以研究下这个来定义哪些异常需要追踪。
然后执行handleFallback逻辑

  • 如果上面两个逻辑都没有配置,那么抛出该异常,
    加下来看看handleFallback处理:
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();

        // Execute fallback function if configured.
        Method fallbackMethod = extractFallbackMethod(pjp, fallback, fallbackClass);
        if (fallbackMethod != null) {
            // Construct args.
            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 the actual exception
                throw e.getTargetException();
            }
        }
        // If fallback is absent, we'll try the defaultFallback if provided.
        return handleDefaultFallback(pjp, defaultFallback, fallbackClass, ex);
    }

handleFallback查找需要执行的falback方法逻辑与blockHandler基本一致,只不过方法的最后一个参数必须是Throwablele类型
比如:

@SentinelResource(fallback="fallbackHandle")
public String test(String name,int age){
	xxxxx
}

在本类中实现fallback方法如下:

@SentinelResource(fallback="fallbackHandle")
public String fallbackHandle(String name,int age,Throwable ex){
	xxxxx
}

如果在其他类中处理:

// Test.java
@SentinelResource(fallback="fallbackHandle",fallbackClass={FallBack.class})
public String test(String name,int age){
	xxxxx
}

//FallBack.java
public String fallbackHandle(String name,int age,Throwable ex){
	xxxxx
}

如果没有配置fallback逻辑,那么会走defaultFallback逻辑,
对于defaultFallback,sentinel中与上面两个处理类似,defaultFallback方法的返回值必须与被注解的方法一样,但是参数不一样,不同点如下:
defaultFallback的方法参数可以有两种,一种是参数列表为空,另外一种参数列表只有一个参数,且该参数类型必须是Throwable类型
在本类中实现fallback方法如下:

@SentinelResource(defaultFallback="defaultFallbackHandle")
public String fallbackHandle(String name,int age,Throwable ex){
	xxxxx
}

在本类中实现defaultFallback方法如下:

public String defaultFallbackHandle(Throwable ex){
	xxxxx
}
// 或者
public String defaultFallbackHandle(){
	xxxxx
}

如果在其他类实现:

// Test.java
@SentinelResource(defaultFallback="defaultFallbackHandle",fallbackClass={FallBack.class})
public String test(String name,int age){
	xxxxx
}

//FallBack.java
public String defaultFallbackHandle(Throwable ex){
	xxxxx
}
//或者
public String defaultFallbackHandle(){
	xxxxx
}

如果异常没有配置处理方法,那么会将该异常抛出,不进行任何处理,由上层业务处理。

最后,需要执行entry.exit方法

总结

最后我们总结一下,在sentinel-springboot-starter基于SentinelResource注解和SentinelResourceAspect来实现熔断限流。
在方法执行前,首先会通Sentinel获取Entry,如果能够获取到,则表明能够访问,执行方法逻辑,如果不能,那么会判断抛出的异常类型。

首先是对于BlockException类型异常,这个就是限流引发的异常,优先处理,处理通过SentinelResource的blockHandler和blockHandlerClass两个属性去处理,具体逻辑为
如果配置了blockHandlerClass,那么就会去blockHandlerClass配置的类中找到blockHandler属性对应的方法,如果没有那么就从当前方法所属类中去找blockHandler属性对应的方法名称,而限流触发执行的方法除了名称要与blockHandler配置的一样外还必须满足:1. 返回值与被注解的方法一样 2. 参数列表与被注解方法列表前面完全一致,但是参数列表要多一个,在最后以为必须为BlockException的参数当前类中的方法可以为静态方法或实例方法,但是blockHandlerClass中方法必须为静态方法,如果配置了blockHandlerClass且当前类中也有对应的blockHandler方法,那么执行blockHandlerClass中方法不会执行当前类中方法。

如果捕获的异常不是BlockException异常,那么会走fallback处理逻辑,与BlockException异常处理基本差不多,方法最后一位必须是Throwable参数,不同的是,如果没有配置fallback,那么还会走defaultFallback处理逻辑
对于defaultFallback,默认会查找defaultFallback,该方法的返回值必须与被注解的方法一致,但是参数必须为空或者只有一个Throwable参数,如果两个都配置了,优先选择空参数列表的方法,如果配置fallbackClass属性,那么会从fallbackClass列表的第一个查找defaultFallback配置的方法。

以上就是springboot中实现sentinel starter的原理以及相关配置使用。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值