美文网首页
sentinel源码解析

sentinel源码解析

作者: 白菜404 | 来源:发表于2022-07-21 18:23 被阅读0次

    背景说明:
    sentinel具体能干哪些事情请移步官方文档:https://sentinelguard.io/zh-cn/docs/introduction.html
    本文章只对使用sentinel过程中可能会碰到问题的点进行源码解读。

    1、@SentinelResource 注解解析

    这里需要注意点的:

    1、fallback和blockHandler的区别,前者是方法异常就会执行的方法,后者是熔断后才会执行的方法。
    2、只指明fallbackClass这个不指明fallback 异常后方法不会执行,同理,只指明blockHandlerClass不指明blockHandler ,熔断后方法也不会执行
    3、指明了fallbackClass 类,fallback指定的方法必须是静态的,且参数保持一致或者末尾加Throws异常类型变量(非必需)(必需是Throws类型,其他Exception异常都不行),不然也不会执行。
    4、指明了blockHandlerClass 类,blockHandler指定的方法必须是静态的,且参数保持一致或者末尾加BlockException异常类型变量(必需)(必需是BlockException类型,其他Exception异常都不行),如果找不到会去找fallback指定的方法,还找不到就找默认的方法,否则不会执行降级

    /*
     * Copyright 1999-2020 Alibaba Group Holding Ltd.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.alibaba.csp.sentinel.annotation;
    
    import com.alibaba.csp.sentinel.EntryType;
    
    import java.lang.annotation.*;
    
    /**
     * The annotation indicates a definition of Sentinel resource.
     *
     * @author Eric Zhao
     * @author zhaoyuguang
     * @since 0.1.1
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    public @interface SentinelResource {
    
        /**
            理解为该熔断降级点的主键key,后续配置熔断规则都是使用该名称来查询和收集,不填默认用熔断方法的全名称作为主键key
         * @return name of the Sentinel resource
         */
        String value() default "";
    
        /**
          标记流量类型是入,还是出?
          这个没太明白,默认out就好了,暂时没用上,后续用上再看
         * @return the entry type (inbound or outbound), outbound by default
         */
        EntryType entryType() default EntryType.OUT;
    
        /**
         * @return the classification (type) of the resource
          这个就是用来标记熔断的是什么类型的方法。web、rpc,还是mq,不重要
         * @since 1.7.0
         */
        int resourceType() default 0;
    
        /**
          注意!!!这个才是熔断后执行的方法,fallback 这个只是方法异常时执行的方法,非熔断
          熔断的后执行的方法名称
         * @return name of the block exception function, empty by default
         */
        String blockHandler() default "";
    
        /**
           熔断后的执行类,不填默认是跟当前被熔断的方法同类
         * The {@code blockHandler} is located in the same class with the original method by default.
         * However, if some methods share the same signature and intend to set the same block handler,
         * then users can set the class where the block handler exists. Note that the block handler method
         * must be static.
         *
         * @return the class where the block handler exists, should not provide more than one classes
         */
        Class<?>[] blockHandlerClass() default {};
    
        /**
          设置熔断的方法执行异常后会执行的方法名,该方法要与设置熔断的方法参数保持一致,或者末尾多个Throws 参数也可以。
         * @return name of the fallback function, empty by default
         */
        String fallback() default "";
    
        /**
          当fallback或者blockHandler找不到的时候会默认执行设置的该方法
         * The {@code defaultFallback} is used as the default universal fallback method.
         * It should not accept any parameters, and the return type should be compatible
         * with the original method.
         *
         * @return name of the default fallback method, empty by default
         * @since 1.6.0
         */
        String defaultFallback() default "";
    
        /**
          设置熔断的方法异常后执行的fallback方法所在的类(跟blockHander不相关)
         * The {@code fallback} is located in the same class with the original method by default.
         * However, if some methods share the same signature and intend to set the same fallback,
         * then users can set the class where the fallback function exists. Note that the shared fallback method
         * must be static.
         *
         * @return the class where the fallback method is located (only single class)
         * @since 1.6.0
         */
        Class<?>[] fallbackClass() default {};
    
        /**
          这个好,这个属性可以控制哪些熔断方法的异常才会进入到fallback设置的方式去执行
         * @return the list of exception classes to trace, {@link Throwable} by default
         * @since 1.5.1
         */
        Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
        
        /**
           设置忽略哪些异常的熔断统计
         * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should
         * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore}
         * will be of higher precedence.
         *
         * @return the list of exception classes to ignore, empty by default
         * @since 1.6.0
         */
        Class<? extends Throwable>[] exceptionsToIgnore() default {};
    }
    
    

    2、SentinelResourceAspect 解析

    这个世sentinel的入口,依着这个入口往下看能看到整个sentinel的实际运行流程

    /*
     * Copyright 1999-2018 Alibaba Group Holding Ltd.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.alibaba.csp.sentinel.annotation.aspectj;
    
    import com.alibaba.csp.sentinel.Entry;
    import com.alibaba.csp.sentinel.EntryType;
    import com.alibaba.csp.sentinel.SphU;
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    import java.lang.reflect.Method;
    
    /**
     * Aspect for methods with {@link SentinelResource} annotation.
     *
     * @author Eric Zhao
     */
    @Aspect
    public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
    
        //基于SentinelResource注解实现的切面方法@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
        public void sentinelResourceAnnotationPointcut() {
        }
    //基于SentinelResource注解实现的切面方法
        @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");
            }
    //获取annotation的value值或者方法全名,作为熔断资源的主键key
            String resourceName = getResourceName(annotation.value(), originMethod);
            EntryType entryType = annotation.entryType();
            int resourceType = annotation.resourceType();
            Entry entry = null;
            try {
                //是否熔断判断,里面涉及到多个熔断slot熔断规则的执行,一个entry可以理解为一个熔断资源对象
                entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
                //这里是实际执行的切面方法,如果上一步命中熔断规则,则直接进入BlockException异常处理,不会再执行该方法
                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())) {
                //标记entry为errorEntry,其实就是把throw对象给保存到entry里面去了
                    traceException(ex);
                  //执行fallback设置的方法
                    return handleFallback(pjp, annotation, ex);
                }
    
                // No fallback function can handle the exception, so throw it out.
                throw ex;
            } finally {
                //这个很重要,一次SphU.entry就必需对应一次entry.exit,
                //改方法主要是清楚本地缓存,上抛本次方法执行的结果给到setinel服务端
                if (entry != null) {
                    entry.exit(1, pjp.getArgs());
                }
            }
        }
    }
    
    

    3、Entry解析
    Entry 使用主要看这个方法:com.alibaba.csp.sentinel.CtSph#entryWithPriority(com.alibaba.csp.sentinel.slotchain.ResourceWrapper, int, boolean, java.lang.Object...)

    private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
            throws BlockException {
            Context context = ContextUtil.getContext();
            if (context instanceof NullContext) {
                // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
                // so here init the entry only. No rule checking will be done.
                return new CtEntry(resourceWrapper, null, context);
            }
    
            if (context == null) {
                // Using default context.
                context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
            }
    
            // Global switch is close, no rule checking will do.
            if (!Constants.ON) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
    
            /*
             * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
             * so no rule checking will be done.
             */
            if (chain == null) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            Entry e = new CtEntry(resourceWrapper, chain, context);
            try {
                //这里就是指责链模式去一个个执行每个slot
                chain.entry(context, resourceWrapper, null, count, prioritized, args);
            } catch (BlockException e1) {
                e.exit(count, args);
                throw e1;
            } catch (Throwable e1) {
                // This should not happen, unless there are errors existing in Sentinel internal.
                RecordLog.info("Sentinel unexpected exception", e1);
            }
            return e;
        }
    

    4、总结

    使用Sentinel 最重要需要注意的一个点:

    如果使用了@SentinelResource,则一定要配置BlockHandler,不然就可能会导致命中了熔断,但是服务未降级,表现出来的异常现象就是:该熔断方法不调用了,但是也没有去调用对应的降级方法,如果你在熔断规则里面设置了熔断时间10分钟,则在10分钟内这个方法都会是不可用状态(排除探针探测到服务又可用的状态)

    相关文章

      网友评论

          本文标题:sentinel源码解析

          本文链接:https://www.haomeiwen.com/subject/zggmirtx.html