流程图和代码处理流程(核心)

* SphU.entry方法主流程图

* Context、Entry、Node关系图

* 整体流程

Entry entry = null;
// 务必保证 finally 会被执行
try {
    // 1、进行Sentinel拦截

// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
    // EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效

// entry方法内会封装了资源的名称StringResourceWrapper(name, type)最终会进入CtSph#entryWithPriority方法执行主流程
    entry = SphU.entry("自定义资源名");
    // 被保护的业务逻辑
    // do something...
} catch (BlockException ex) {
    // 2、抛出BlockException基类异常。说明业务资源访问阻止,被限流或被降级
    //   进行相应的处理操作用户可以自定义的处理,如进行降级或者返回异常
} catch (Exception ex) {

// 3、其他异常,如业务代码抛出的异常。也可以向上抛出
    //   若需要配置降级规则,需要通过这种方式记录业务异常
    Tracer.traceEntry(ex, entry);
} finally {
    // 4、正常执行完成。退出的情况

//  务必保证 exit,务必保证每个 entry  exit 配对

//  (同下)this.exit(count, new Object[0];
    if (entry != null) {
        entry.exit();
    }
}

* SphU.entry最终会进入CtSph类的entryWithPriority方法

//CtSph
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
    // 1. 获取当前线程Context
    //     * ContextUtil里存在ThreadLocal<Context> contextHolder保存了当前线程的Context;
    //     * ContextUtil里存在Map<String, DefaultNode>contextNameNodeMap 记录所有Context对应的EntranceNode
    Context context = ContextUtil.getContext();
    // Context数量超过了阈值,不会做任何规则校验
    //  一个进程中,Sentinel只能允许最多2000Context(硬编码),超出2000个都会返回NullContext,后续不会执行任何规则校验,直接放行;
    if (context instanceof NullContext) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 2. 如果用户没有主动创建Context就会创建一个默认上下文sentinel_default_context
    //  其底层调用ContextUtil#trueEnter进行创建
    //  * 先判断Context上限,超过就返回NullContext,不会创建实际Context
    //  * 创建EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);作为全局Constants.ROOT的子节点,再put(上下文名称,EntranceNode)进去contextNameNodeMap
    //  * 创建Context并放入ThreadLocal
    //        context = new Context(EntranceNode, name);
    //        context.setOrigin(origin);
    //        contextHolder.set(context)
    if (context == null) {
        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

    if (!Constants.ON) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 3. 获取Slot
    //  *lookProcessChain方法加载Resource对应ProcessorSlotChainProcessorSlotChain包含了所有通过SPI(META-INF/services/")机制加载的ProcessorSlot
    //  * 全局的Map<ResourceWrapper, ProcessorSlotChain> chainMap保存着资源名称对应的Slot链,同一个nameResource资源对应同一个ProcessorSlotChain实例
    //  * 一个进程中,Sentinel只能允许最多6000Resource(硬编码),超出6000lookProcessChain方法会返回null,后续不会执行任何规则校验,直接放行;
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 4. 构造CtEntry,构造时将这个Entry接入Context中的Entry链表尾部,封装好对应的Slot链和Context
    //    再一个Slot内,每次当前Slot执行完自己的职责后(责任链),会调用抽象类中的fireEntry方法,执行下一个Slotentry方法。
    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        // 5. 执行所有规则校验
        //  * 默认存在的Slot链如下
        //      NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储。
        //      ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息。
        //      StatisticSlot:使用Node记录指标信息,如RTPass/Block Count,为后续规则校验提供数据支撑。
        //      AuthoritySlot:授权规则校验
        //      SystemSlot:系统规则校验
        //      ParamFlowSlot:热点参数流控规则校验
        //      FlowSlot:流控规则校验
        //      DegradeSlot:降级规则校验
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        //6. 以上步骤如果发生BlockException,需要先执行exit,再抛出异常业务代码外层也会在finally里调用exit
        //   * exit底层调用CtEntryexitForContext
        //     1. 执行所有Slotexit方法只有实现了自己exit逻辑,其他都是放行

- StatisticSlot当ENtry的BlckErro为空,没有异常发生,那么进行Node的compelet记录----业务代码发生异常的场景

- DegradeSlot当ENtry的BlckErro为空,没有异常发生那么进行断路器的compelet记录----业务代码发生异常的场景
        //     2. 执行所有exitHandlers(降级规则会用到)callExitHandlersAndCleanUp(context)
        //        执行当前Entry注册所有的exitHandlers

//        在AbstractCircuitBreaker的fromOpenToHalfOpen里,从打开->半开时,就会注册一个exitHandler如果因为异常原因的退出,那么会重新把断路器打开
        //     3. contextentry链表移除当前entry
        //     4. 当前entry.context = null,防止重复exit
        //
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // 内部错误,不太可能发生
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}

创建/获取线程上下文Context

概述

public class Context {
    // 上下文名称
    private final String name;
    // EntranceNode 入口节点 --- 一般不同的Context其entranceNode不同,当用户不手动创建而使用默认的sentinel_default_context的上下文,其内会公用同一个EntranceNode
    private DefaultNode entranceNode;
    // 当前Entry --- 链表
    private Entry curEntry;
    // 来源
    private String origin = "";
}

表示一次请求线程处理中的上下文信息,包括各个组件的引用

* 上下文名称,作为上下文的唯一标志

* EntranceNode 入口节点,由此可以找到整个线程执行过程中所创建的数据统计结点Node链

* 当前Entry结点,整个线程的每次调用拦截SphU.entry都会生成一个Entry,从而形成Entry链

* 来源标志,一个Context中只能存在一个来源,表示调用方

  例如通过如下方法最终执行trueEnter(ContextName, origin),在这里就明确指定了来源,例如使用请求总的IP

String origin = parseOrigin(sRequest);
    ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME, origin);

创建Context(包含EntranceNode + curEntry)

* 可以在SphU.entry之前使用ContextUtil.enter(上下文名称,来源名称)创建Context,其底层调用ContextUtil.trueEnter(String name, String origin)

* 如果没有收到创建,那么SphU.entry中会使用一个名称为sentinel_default_context的默认上下文,其底层调用ContextUtil.trueEnter(sentinel_default_contex, “”)

  注:所有名称为sentinel_default_context的默认创建的上下文都会公用同一个EntranceNode

* 在ContextUtil中创建

①一个本地线程变量contextHolder表示当前线程的Context

②contextNameNodeMap表示记录所有Context对应的EntranceNode ,key就是上下文名称

public class ContextUtil {
        private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
        private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
    }

* ContextUtil.enter代码流程

  ①先再次判断下当前线程上下文是否存在,存在就直接返回了

  ②获取上下文name对应的EntranceNode,localCacheNameMap/contextNameNodeMap 如果不存在需要创建。

如果是默认上下文的,会在加载时就创建了EntranceNode放入localCacheNameMap

  ③如果上下文数量超过MAX_CONTEXT_NAME_SIZE(2000)个,返回NullContext,不会创建实际Context

  ④创建入口EntranceNode,先对localCacheNameMap加锁,创建后放入localCacheNameMap中,并作为全局根结点Constants.ROOT的子节点

     node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
     Constants.ROOT.addChild(node);

     其中StringResourceWrapper封装了上下文名称和EntryType(表示调用方向)

  ⑤最后new Context(EntranceNode, name),设置好来源字段、把当前Context设置到本地线程contextHolder

     

public static Context enter(String name, String origin) {
    if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
        throw new ContextNameDefineException();
    }
    return trueEnter(name, origin);
}


protected static Context trueEnter(String name, String origin) {
    // 1. 获取当前线程上下文,如果存在的话,不会创建新的上下文
    Context context = contextHolder.get();
    if (context == null) {
        // 2. 获取上下文name对应的EntranceNode,如果不存在需要创建
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        DefaultNode node = localCacheNameMap.get(name);
        if (node == null) {
            if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                // 3. 如果上下文数量超过MAX_CONTEXT_NAME_SIZE2000)个,返回NullContext,不会创建实际Context
                setNullContext();
                return NULL_CONTEXT;
            } else {
                LOCK.lock();
                try {
                    node = contextNameNodeMap.get(name);
                    if (node == null) {
                        if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                            setNullContext();
                            return NULL_CONTEXT;
                        } else {
                            // 4. 创建name对应EntranceNode
                            node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                            Constants.ROOT.addChild(node);

                            Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                            newMap.putAll(contextNameNodeMap);
                            newMap.put(name, node);
                            contextNameNodeMap = newMap;
                        }
                    }
                } finally {
                    LOCK.unlock();
                }
            }
        }
        // 5. 创建Context并放入ThreadLocal
        context = new Context(node, name);
        context.setOrigin(origin);
        contextHolder.set(context);
    }

    return context;
}

获取DefaultProcessorSlotChain(相同资源共用相同的Chain)

同一个name的资源,会使用同一个ProcessorSlotChain

在管理配置对一个name资源配置的所有规则会形成一个插槽链

Sentinel的SPI

* @Spi表示一个指定spi接口的实现类

  注解添加在基于java SPI配置的实现类中,可以对这些服务提供的实现类进行控制,

  比如别名互斥、单例、默认实现、优先级,这里还能都在SpiLoader<S>中进行处理

  当一个类被此注解标志后,就表示此类作为某个SPI接口的实现类

public @interface Spi {
    String value() default ""; //别名,通过设置别名,同一个SpiLoader中,相同别名的实现类只能存在一个
    boolean isSingleton() default true; //是否单例,默认true
    boolean isDefault() default false; //是否是默认实现类,默认false,同一个SpiLoader中,只能有一个默认实现类;
    int order() default 0;    //优先级
    int ORDER_HIGHEST = Integer.MIN_VALUE;
    int ORDER_LOWEST = Integer.MAX_VALUE;
}

* SpiLoader<S>加载器

  - 一个Spi接口可以对应多个SpiLoader,但是一般对应一个接口

- 基于JAVA SPI中获取到类型的所有实现类,封装到一个SpiLoader类中,其中经过处理后的结果存于各个变量中

  例如别名映射、排序

  - 通过SpiLoader.of(S.class)可以获取到S类型的所有服务提供类对象。

  public final class SpiLoader<S> {
    // 加载SPI配置文件路径
    private static final String SPI_FILE_PREFIX = "META-INF/services/";
    // SPI接口 - SpiLoader实现类
    private static final ConcurrentHashMap<String, SpiLoader> SPI_LOADER_MAP = new ConcurrentHashMap<>();
    // 当前SpiLoader缓存的 SPI接口实现类
    private final List<Class<? extends S>> classList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());
    // 当前SpiLoader缓存的 SPI接口实现类(有序)
    private final List<Class<? extends S>> sortedClassList = Collections.synchronizedList(new ArrayList<Class<? extends S>>());
    // 当前SpiLoader缓存的 SPI接口实现类别名 - SPI接口实现类
    private final ConcurrentHashMap<String, Class<? extends S>> classMap = new ConcurrentHashMap<>();
    // 当前SpiLoader缓存的 单例map k-className v-单例
    private final ConcurrentHashMap<String, S> singletonMap = new ConcurrentHashMap<>();
    // 当前SpiLoader是否已经加载所有SPI接口实现类
    private final AtomicBoolean loaded = new AtomicBoolean(false);
    // 当前SpiLoader对应Spi接口的默认实现类
    private Class<? extends S> defaultClass = null;
    // 当前SpiLoader对应Spi接口
    private Class<S> service;
}

* 例如我们自定义一个Slot类,这时默认的几个Slot的顺序是不能去改变,我们需要把自定义的Slot的优先级作为最低。

  在项目跟目录下创建以下文件,并在文件中写入上面定义的PrintLoveSlot的全限定类名。

  

  代码如下,在FireEntry前后输出I Love You。

@Spi(order = -6500)
public class PrintLoveSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
            throws Throwable {
        System.out.println("I Love You,start");
        fireEntry(context, resourceWrapper, obj, count, prioritized, args);
        System.out.println("I Love You,end");
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }
}

获取Slot链流程

ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);

* 同一个name的资源对应同一个ProcessorSlotChain实例(ResourceWrapper的equals和hasCode方法);

* 一个进程中,Sentinel只能允许最多6000个Resource(硬编码),超出6000个lookProcessChain方法会返回null,后续不会执行任何规则校验,直接放行;

* SPI机制加载所有ProcessorSlot,构造为DefaultProcessorSlotChain返回

- SpiLoader.of(SlotChainBuilder.class):基于SPI的生成一个SpiLoader对象,里面加载了SlotChainBuilder类型的实现类

- 再调用SpiLoader的loadFirstInstanceOrDefault,默认返回了DefaultSlotChainBuilder

* DefaultSlotChainBuilder.build方法获取 ProcessorSlotChain

- 和上面加载SlotChainBuilder的过程类似,这里是加载ProcessorSlot类型的实现类,并进行排序后返回

- SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();

  - 下图可以看到 META-INF/services/ + ProcessorSlot.fullClassName 的文件内容,发现有8个Slot。

   loadInstanceListSorted 就是加载并排序,在每个Slot上面都标有@Order,加载后根据Order进行排序,生成有序列表。这里的过程很像Spring中对PostProcessor的处理。

   

* 代码流程

private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap = new HashMap<ResourceWrapper, ProcessorSlotChain>()


ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
    // 1. 同一个name的资源,会使用同一个ProcessorSlotChain
    ProcessorSlotChain chain = chainMap.get(resourceWrapper);
    if (chain == null) {
        synchronized (LOCK) {
            chain = chainMap.get(resourceWrapper);
            if (chain == null) {
                // 2. 如果资源数量超过了MAX_SLOT_CHAIN_SIZE6000),则返回空,不做规则校验
                if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                    return null;
                }
                // 3. SPI机制加载所有ProcessorSlot,构造为DefaultProcessorSlotChain返回
                chain = SlotChainProvider.newSlotChain();
                Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                        chainMap.size() + 1);
                newMap.putAll(chainMap);
                newMap.put(resourceWrapper, chain);
                chainMap = newMap;
            }
        }
    }
    return chain;
}

public static ProcessorSlotChain newSlotChain() {
    if (slotChainBuilder != null) {
        return slotChainBuilder.build();
    }
    slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
    return slotChainBuilder.build();
}

@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                continue;
            }
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }
        return chain;
    }
}

ProcessorSlot架构

* ProcessorSlot接口,接口方法如下

 ①entry和exit:entry表示此slot的规则校验逻辑的处理,exit表示整个链接流程退出时的处理

 ②fireEntry和fireExit:提供责任链设计模式,调用下一个结点的entry和exit

* AbstractLinkedProcessorSlot类主要实现责任链的部分功能

①保存此slot实现类的下一个结点next,使用setNext

②实现了fireEntry和fireExit以提供责任链设计模式

- fireEntry:调用下一个结点的next.transformEntry即next.entry

- fireExit:调用下一个结点的next.exit

  public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {

    //下一个Slot
    private AbstractLinkedProcessorSlot<?> next = null;
    public AbstractLinkedProcessorSlot<?> getNext() {return next;}
    public void setNext(AbstractLinkedProcessorSlot<?> next) {this.next = next;}
    
    //调用下一个结点的entry
    @Override
    public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
            throws Throwable {
        if (next != null) {
            //实际调用了next.entry
            next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
        }
    }
    void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)throws Throwable {
        T t = (T)o;
        entry(context, resourceWrapper, t, count, prioritized, args);
    }

    //调用下一个结点的entryexit
    @Override
    public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        if (next != null) {
            next.exit(context, resourceWrapper, count, args);
        }
    }
    
}

* DefaultProcessorSlotChain类构建了责任链,同样需要实现ProcessorSlot接口

  ①创建了first头Slot,其entry和exit的实现很简单,直接调用了父类AbstractLinkedProcessorSlot的fireEntry和fireExit以触发下一个结点。而没有自己的特殊规则处理

  ②提供addFirst/addLast:把每一个加进来的Slot进行关联,即设置了每一个Slot的next(在AbstractLinkedProcessorSlot中),从而形成一条单向的链表

  ③entry和exit的实现,调用first结点的entry和exit

public class DefaultProcessorSlotChain extends ProcessorSlotChain {

    //1、头结点
    //entryexit的实现很简单,直接调用了父类AbstractLinkedProcessorSlotfireEntryfireExit。而没有自己的特殊规则处理
    AbstractLinkedProcessorSlot<?> first = new AbstractLinkedProcessorSlot<Object>() {
        @Override
        public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)throws Throwable {
            super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
        }
        @Override
        public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
            super.fireExit(context, resourceWrapper, count, args);
        }
    };
    AbstractLinkedProcessorSlot<?> end = first;


    //2、尾插法,把即将加入的结点添加到end结点的next变量
    @Override
    public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        end.setNext(protocolProcessor);
        end = protocolProcessor;
    }
    @Override
    public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        protocolProcessor.setNext(first.getNext());
        first.setNext(protocolProcessor);
        if (end == first) {
            end = protocolProcessor;
        }
    }
    @Override
    public void setNext(AbstractLinkedProcessorSlot<?> next) {
        addLast(next);
    }
    @Override
    public AbstractLinkedProcessorSlot<?> getNext() {
        return first.getNext();
    }

    //3SlotChainentryexit的实现
    //调用first结点的entryexit
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
            throws Throwable {
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        first.exit(context, resourceWrapper, count, args);
    }

}

* 具体的实现类,如

①每一个slot实现类都必须实现entry和exit,此时进行规则校验时,此slot的规则处理以及整个链接流程退出时的处理

②在entry和exit的最后,都需要调用父类的fireEntry和fireExit以触发下一个结点的方法

public class AuthoritySlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
            throws Throwable {
        //自己的校验晾逻辑如果不通过,就会抛出AuthorityException 异常,从而终止整个流程
        checkBlackWhiteAuthority(resourceWrapper, context);
        //父类方法,调用下一个结点的entry
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        //父类方法,调用下一个结点的exit
        fireExit(context, resourceWrapper, count, args);
    }

    void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
        //
    }
}

创建Entry(可看做SphU.entry的抽象)

概述

实现类tEntry的作用

* CtEntry中聚合了Context、Slot、Resource、以及保存了当前Context中对应的originNode、和当前Resource对应的DefaultNode

* 一个CtEntry表示一个SphU.entry的抽象

  运行进行嵌套的调用SphU.entry进行拦截的,这样的话SphU.entry在不同代码调用点都可以当做一个CtEntry,并会形成一个调用链

* 每一个Entry也保存了前后结点的引用

* 提供了exit的实现,以在校验过程中出现BlockException或者正常退出时,做一些处理

创建CtEntry加入Context的Entry调用链

Entry e = new CtEntry(resourceWrapper, chain, context);

*在构造器中进行操作

- 设置了当前线程Context、当前资源的Slot链、Resource,

- 通过setUpEntryFor,将当前Entry前插加入了当前线程Context中的Entry调用链

  表示当前正在执行的是本次SphU.entry调用

class CtEntry extends Entry {
    // 上一个入口Entry
    protected Entry parent = null;
    // 下一个Entry
    protected Entry child = null;

    // Slot插槽
    protected ProcessorSlot<Object> chain;
    // 上下文
    protected Context context;
    protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;

    CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
        //设置了父类的两个变量
        //this.resourceWrapper = resourceWrapper;
        //this.createTime = TimeUtil.currentTimeMillis();

super(resourceWrapper);
        this.chain = chain;
        this.context = context;
        setUpEntryFor(context);
    }
    // 将当前Entry加入上下文Entry链表
    private void setUpEntryFor(Context context) {
        if (context instanceof NullContext) {
            return;
        }
        this.parent = context.getCurEntry();
        if (parent != null) {
            ((CtEntry) parent).child = this;
        }
        context.setCurEntry(this);
    }
}

开始执行Slot链

chain.entry(context, resourceWrapper, null, count, prioritized, args);

* chain即为之前到的当前资源对应的Slot链,调用其entry方法会一次执行责任链上的每一个Slot,默认存在的Slot链执行顺序

  - NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储。

  - ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息。

  - StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑。

  - AuthoritySlot:授权规则校验

  - SystemSlot:系统规则校验

  - ParamFlowSlot:热点参数流控规则校验

  - FlowSlot:流控规则校验

  - DegradeSlot:降级规则校验

  

正常结束/异常捕捉

* 正常执行完成

- 务必保证 exit,务必保证每个 entry 与 exit 配对

- 执行同下异常处理的entry.exit()

* 如果在执行期间抛出异常,需要特定的捕捉BlockException类型的异常,这些异常表示当前请求被规则拦截了

  用户可以对其进行自定义的异常处理,当时需要先执行exit,再抛出异常给到最外层

  

* Entry的exit(count, args)的处理逻辑

 ①如果发生异常的Entry不是当前上下文执行的Entry,即是内嵌调用Entry发生错误了,那么需要依次调用外层的Entry,一一进行exit

 ②如果是当前执行的Entry发生异常

   - 逆序的执行所有Slot的exit方法

   - 执行当前Entry上所有的exitHandlers,这主要是为了Degrade降级规则的断路器服务

   - context中entry链表移除当前entry

   - 当前entry.context = null,防止重复exit

 ③接着向外抛出BlockException

try {
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        //6. 以上步骤如果发生BlockException,需要先执行exit,再抛出异常
        //   * exit底层调用CtEntryexitForContext
        //     1. 逆序的执行所有Slotexit方法
        //     2. 执行所有exitHandlers(降级规则会用到)callExitHandlersAndCleanUp(context)
        //        执行当前Entry上所有的exitHandlers,这主要是为了Degrade降级规则的断路器服务
        //     3. contextentry链表移除当前entry
        //     4. 当前entry.context = null,防止重复exit
        //
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        // 内部错误,不太可能发生
        RecordLog.info("Sentinel unexpected exception", e1);
    }

@Override
public void exit(int count, Object... args) throws ErrorEntryFreeException {
    trueExit(count, args);
}

protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
    if (context != null) {
        if (context instanceof NullContext) {
            return;
        }
        //如果发生异常的Entry不是当前上下文执行的Entry,即是内嵌调用Entry发生错误了,那么需要依次调用外层的Entry,一一进行exit
        if (context.getCurEntry() != this) {
            // Clean previous call stack.
            CtEntry e = (CtEntry)context.getCurEntry();
            while (e != null) {
                e.exit(count, args);
                e = (CtEntry)e.parent;
            };
            throw new ErrorEntryFreeException();
        } else {
            //如果是当前执行的Entry发生异常
            //先执行Slot链的exit,会递归的执行每一个Slotexit
            if (chain != null) {
                chain.exit(context, resourceWrapper, count, args);
            }
            // Restore the call stack.
            context.setCurEntry(parent);
            if (parent != null) {
                ((CtEntry)parent).child = null;
            }
            if (parent == null) {
                if (ContextUtil.isDefaultContext(context)) {
                    ContextUtil.exit();
                }
            }
            clearEntryContext();
        }
    }
}

更多推荐