Sentinel源码分析--@SentinelResource

目录

1.@SentinelResource

2.SentinelResourceAspect

3.DefaultProcessorSlotChain

4.NodeSelectorSlot

5.ClusterBuilderSlot

6.LogSlot

7.StatisticSlot

7.1 核心流程

7.2 计算时间窗口请求量统计

8.AuthoritySlot

9.SystemSlot

10.ParamFlowSlot

11.FlowSlot

11.1 核心流程

11.2 滑动时间窗口

11.3 漏桶

11.4 令牌桶

12. DegradeSlot

11.1 CircuitBreaker

11.2 触发断路器


1.@SentinelResource

对于@SentinelResource注解,相信大家也不会陌生,该注解是在sentinel里面担当起标明该方式受sentinel保护的业务,属于方法级别的注解

@SentinelResource注解的实现是用到了spring的aop技术,在 SentinelResourceAspect类去实现相关的逻辑

2.SentinelResourceAspect

/**
 * 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() {
    }

    //环绕增强
    @Around("sentinelResourceAnnotationPointcut()")
    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
        // 获取受保护的方法
        Method originMethod = resolveMethod(pjp);

        //获取到方法是否加了SentinelResource注解
        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,调用slot链的相关规则
            entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
            // 执行受保护的方法
            return pjp.proceed();
        } catch (BlockException ex) {
            //执行handleBlockException方法
            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())) {
                traceException(ex);
                //执行自定义回调异常代码
                return handleFallback(pjp, annotation, ex);
            }

            // No fallback function can handle the exception, so throw it out.
            throw ex;
        } finally {
            if (entry != null) {
                //结束退出slot链的调用
                entry.exit(1, pjp.getArgs());
            }
        }
    }
}

简单来说,@SentinelResource注解就是一个标记,而Sentinel基于AOP思想,对被标记的方法做环绕增强,完成资源(Entry)的创建。

进入SphU.entry()

/**
     * 记录统计数据并对给定资源执行规则检查。
     *
     * @param name         受保护资源的唯一名称
     * @param trafficType  流量类型(入站、出站或内部)。这是使用
     *                     标记系统不稳定时是否可以阻塞,
     *                     只有入站的流量可以被阻塞
     * @param resourceType 资源分类 (e.g. Web or RPC)
     * @param args         参数流控制或定制槽的参数
     */
    public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args)
        throws BlockException {
        return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
    }

此处的Env会在内部实现Sentinel一些初始化操作,通过SPI机制去加载对应的资源,点击此处可以跳转查看Env的代码解析

进入entryWithType(name, resourceType, entryType, count, false, args)方法

 @Override
    public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
                               Object[] args) throws BlockException {
        //将资源名称等基本信息封装成为一个StringResourceWrapper对象
        StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
        return entryWithPriority(resource, count, prioritized, args);
    }

进入 entryWithPriority 方法

private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        //获取到当前线程上下文context
        Context context = ContextUtil.getContext();
        if (context instanceof NullContext) {
            // 指示上下文的数量已经超过阈值,这里只初始化条目。不会进行规则检查
            return new CtEntry(resourceWrapper, null, context);
        }

        //如果上下文为空的话,那么创建出一个默认的sentinel_default_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,同一个资源,会创建一个执行链,放入缓存
        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

        // 表示超出资源(槽链)的数量,所以不会进行规则检查。
        if (chain == null) {
            return new CtEntry(resourceWrapper, null, context);
        }

        // 创建 Entry,并将 resource、chain、context 记录在 Entry中
        Entry e = new CtEntry(resourceWrapper, chain, context);
        try {
            // 执行 slotChain
            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;
    }

该方法的话:

1.获取到当前线程的上下文对象,如果没有上下文对象,如果上下文对象为空,就创建出一个默认的sentinel_default_context上下文对象

2.获取到processorSlotChain,同一个资源,会创建一个执行链,放入缓存

3.执行 slotChain

进入到lookProcessChain(resourceWrapper)

ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
        //获取当前资源是否有责任链
        ProcessorSlotChain chain = chainMap.get(resourceWrapper);
        //此处使用了双重检验模式--单例常用的一种
        if (chain == null) {
            synchronized (LOCK) {
                //再次尝试获取是否有值
                chain = chainMap.get(resourceWrapper);
                if (chain == null) {
                    // 如果当前的chain长度大于等于6000,就直接不走规则
                    if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                        return null;
                    }
                    //创建新的责任链数据
                    chain = SlotChainProvider.newSlotChain();
                    //此处map的代码感觉很奇怪,觉得原作者是想chainMap.size() + 1扩容,但是忽略了map的扩容机制
                    Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                        chainMap.size() + 1);
                    newMap.putAll(chainMap);
                    newMap.put(resourceWrapper, chain);
                    //将原chainMap指向新的map,从而达到替换
                    chainMap = newMap;
                }
            }
        }
        return chain;
    }

此处方法的话,逻辑比较简单:

先尝试从map获取数据,获取不到就采用双重检验模式去操作,会创建出当前资源所需要的SlotChain数据链,然后返回到外面

进入SlotChainProvider.newSlotChain()

public static ProcessorSlotChain newSlotChain() {
        //判断是不是非第一次加载,是就走构造方法调用
        if (slotChainBuilder != null) {
            return slotChainBuilder.build();
        }

        // 利用SPI机制加载位于META-INF/service/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder下面的资源
        //此处只加载了到了一个DefaultSlotChainBuilder类
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
        //一般都不会失败的因为这个文件已经固定好了,这里判空也就是为了增加代码的一个健壮性
        if (slotChainBuilder == null) {
            // Should not go through here.
            RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
            slotChainBuilder = new DefaultSlotChainBuilder();
        } else {
            RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
                slotChainBuilder.getClass().getCanonicalName());
        }
        return slotChainBuilder.build();
    }

该方法:

1.判断SPI机制是否加载过slotChainBuilder,没有加载过就去加载

2.调用slotChainBuilder.build()去加载对应的规则链

进入slotChainBuilder.build()

public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        //利用SPI机制加载位于META-INF/service/com.alibaba.csp.sentinel.slotchain.ProcessorSlot下面的资源
        //官方也推荐如果我们想自定义slot也可以放进对应的文件里
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        //遍历把这些slot放进chain里
        for (ProcessorSlot slot : sortedSlotList) {
            //如果slot没有继承AbstractLinkedProcessorSlot类,那么不能当做slot节点
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
                continue;
            }
            //保存
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

这图可以看出chain的first节点就是在上面加载的 DefaultSlotChainBuilder类,因为是第一个放进去的,后面的节点都是next,为什么end节点是DegradeSlot呢?请看下面代码

public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        end.setNext(protocolProcessor);
        end = protocolProcessor;
    }

总结该方法:

也就简单的通过SPI机制按照顺序加载到slot,然后遍历存放到AbstractLinkedProcessorSlot的next节点,从名字上也可以得知是链式调用。

注:对于slot节点,在官方的wiki也指出

下面开启了在上面获取到chain的slot链式的调用

3.DefaultProcessorSlotChain

进入到DefaultProcessorSlotChain#entry方法:

@Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        //first,也就是责任链的第一个slot
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }

这里的first,其实也就是充当一个入口,并没有很多复杂逻辑,其实真正算是first节点的调用可以理解从NodeSelectorSlot节点开始,不过基于责任链的形式,只要记住下一个next节点调用即可

4.NodeSelectorSlot

进入NodeSelectorSlot#entry方法

public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        //尝试获取当前资源的DefaultNode
        DefaultNode node = map.get(context.getName());
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                if (node == null) {
                    //如果为空,为当前资源创建一个新的DefaultNode
                    node = new DefaultNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江边小子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值