sentinel源码解析

背景说明:
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[] 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[] 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[] 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 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源码解析)