Sentinel源码(二)entry和exit

前言

本章基于Sentinel1.8,分析Sph.entry和Entry.exit的执行流程。

1、SPI

首先了解一下Sentinel的SPI(Service Provider Interface)机制。

Sentinel的Spi机制和JDK的Spi机制类似,它相对于JDK的扩展点都浓缩在了Spi注解里。

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

综上Sentinel的SPI机制支持:别名互斥、单例、默认实现、优先级。

SpiLoader负责加载针对与泛型S的SPI接口的实现类,并缓存在几个成员变量中,以此来实现上述四个功能。

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;
}
复制代码

比如Sentinel的各类ProcessorSlot插槽就是通过SPI机制加载的。

SpiLoader.of(ProcessorSlot.class)创建了DefaultSlotChainBuilder,负责加载ProcessorSlot接口对应的实现类。

@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;
    }
}
复制代码

SpiLoader的逻辑较为简单,和JDK的ServiceLoader源码极其相似,直接略过。

2、SphO or SphU

使用Sentinel的一个简单案例如下:

Entry entry = null;
try {
    // 获取Entry
    entry = SphU.entry(KEY);
} catch (BlockException e1) {
    // 规则校验失败,发生BlockException
    block.incrementAndGet();
} catch (Exception e2) {
    // 业务异常处理
} finally {
    total.incrementAndGet();
    // 释放Entry
    if (entry != null) {
        entry.exit();
    }
}
复制代码

SphO的entry方法,捕获了BlockException,如果Sentinel规则校验没通过,会返回false。

// SphO.java
public static boolean entry(Method method, EntryType trafficType, int batchCount, Object... args) {
    try {
        Env.sph.entry(method, trafficType, batchCount, args);
    } catch (BlockException e) {
        return false;
    } catch (Throwable e) {
        RecordLog.warn("SphO fatal error", e);
        return true;
    }
    return true;
}
复制代码

SphU的entry方法,直接调用Env.sph.entry,没有做任何处理。

// SphU.java
public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args)
  throws BlockException {
  return Env.sph.entry(name, trafficType, batchCount, args);
}
复制代码

两者底层都是调用Env里的static变量CtSph实例的entry方法。

public class Env {
    public static final Sph sph = new CtSph();
}
复制代码

3、CtSph

无论是String类型资源,还是Method类型资源,进入CtSph后,都会封装为StringResourceWrapper或MethodResourceWrapper。

// CtSph.java
@Override
public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
    StringResourceWrapper resource = new StringResourceWrapper(name, type);
    return entry(resource, count, args);
}
复制代码

最终会进入entryWithPriority方法,prioritized参数默认为false。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    // 1. 获取当前线程Context
    Context context = ContextUtil.getContext();
    // 当Context数量超过了阈值,不会做任何规则校验
    if (context instanceof NullContext) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 2. 如果用户没有主动创建Context,使用默认上下文sentinel_default_context
    if (context == null) {
        context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }

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

    // 3. 获取Slot链
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }

    // 4. 构造CtEntry,构造时将这个Entry接入Context中的Entry链表尾部
    Entry e = new CtEntry(resourceWrapper, chain, context);
    try {
        // 5. 执行所有规则校验
        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;
}
复制代码

CtSph.entryWithPriority可以分为5步,其中前两步是在获取Context上下文,第三步是获取所有需要执行的ProcessorSlot,第四步是构造Entry,第五步执行所有ProcessorSlot。

接下来一步一步分析entry方法。

4、Context

CtSph.entryWithPriority的前两步都涉及Context上下文。

Context通过ThreadLocal存储在ContextUtil中,且每个Context的名称都唯一对应一个EntranceNode。

public class ContextUtil {
    private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
    // k=context.name v=EntranceNode
    private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
}
复制代码

ContextUtil初始化时,会创建一个名为sentinel_default_context的Context对应的EntranceNode。

并会把这个EntranceNode加入到Constants.ROOT节点的子节点中。

// ContextUtil.java
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
static {
    initDefaultContext();
}
private static void initDefaultContext() {
    // sentinel_default_context
    String defaultContextName = Constants.CONTEXT_DEFAULT_NAME;
    EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null);
    Constants.ROOT.addChild(node);
    contextNameNodeMap.put(defaultContextName, node);
}
复制代码

CtSph.entryWithPriority的第一步,获取当前线程持有的Context上下文。

如果当前线程持有的Context是NullContext,则不会执行任何规则校验(CtEntry中SlotChain为空),返回一个CtEntry。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) {
         // 1. 获取当前线程Context
    Context context = ContextUtil.getContext();
    // 当Context数量超过了阈值,不会做任何规则校验
    if (context instanceof NullContext) {
        // 第二个参数为null,CtEntry中SlotChain为空,代表不会做任何规则校验
        return new CtEntry(resourceWrapper, null, context);
    }
}
// ContextUtil.java
private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
public static Context getContext() {
    return contextHolder.get();
}
复制代码

CtSph.entryWithPriority的第二步,如果当前线程没有对应的上下文Context,则设置当前线程上下文为sentinel_default_context默认上下文。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
    // ..

    // 2. 如果用户没有主动创建Context,使用默认上下文sentinel_default_context
    if (context == null) {
      context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
    }
}
复制代码

InternalContextUtil.internalEnter底层调用的是ContextUtil#trueEnter方法,这是创建上下文的核心方法。

// InternalContextUtil.java
/**
* Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name.
*/
private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
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_SIZE(2000)个,返回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;
}
复制代码

从上述代码可以得知:

  1. 每个上下文name对应一个同样name的EntranceNode,存储在InternalContextUtil的静态变量contextNameNodeMap中,key是上下文名称,value是EntranceNode;
  2. 一个进程中,Sentinel只能允许最多2000个Context(硬编码),超出2000个都会返回NullContext,后续不会执行任何规则校验,直接放行;

用户代码可以执行InternalContextUtil的公共enter方法,创建非默认上下文。

// InternalContextUtil.java
public static Context enter(String name, String origin) {
    if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) {
        throw new ContextNameDefineException(
            "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!");
    }
    return trueEnter(name, origin);
}
复制代码

5、ProcessorSlot

CtSph.entryWithPriority的第三步,根据资源name获取需要执行的ProcessorSlot链,如果返回chain为空,直接返回CtEntry,不会做规则校验。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
    // ...
    // 3. 获取Slot链
    ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
    if (chain == null) {
        return new CtEntry(resourceWrapper, null, context);
    }
    // ...
}
复制代码

lookProcessChain方法加载Resource对应ProcessorSlotChain,ProcessorSlotChain包含了所有通过SPI机制加载的ProcessorSlot,用于后续做这个资源的规则校验。

需要注意的是:

  1. 同一个name的资源对应同一个ProcessorSlotChain实例(ResourceWrapper的equals和hasCode方法);
  2. 一个进程中,Sentinel只能允许最多6000个Resource(硬编码),超出6000个lookProcessChain方法会返回null,后续不会执行任何规则校验,直接放行;
// CtSph.java
/**
* Same resource({@link ResourceWrapper#equals(Object)}) will share the same
* {@link ProcessorSlotChain}, no matter in which {@link Context}.
*/
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_SIZE(6000),则返回空,不做规则校验
                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;
}
复制代码

6、CtEntry

CtSph.entryWithPriority的第四步,构造CtEntry,这个CtEntry即为返回给用户代码的Entry。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) throws BlockException {
    // ...
    // 4. 构造CtEntry,构造时将这个Entry接入Context中的Entry链表尾部
    Entry e = new CtEntry(resourceWrapper, chain, context);
    // ...
}
复制代码

CtEntry中聚合了Context、Slot、Resource,并且在构造方法中,通过setUpEntryFor,将当前Entry加入了Context中的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) {
        super(resourceWrapper);
        this.chain = chain;
        this.context = context;
        setUpEntryFor(context);
    }
    // 将当前Entry加入上下文Entry链表
    private void setUpEntryFor(Context context) {
        // The entry should not be associated to NullContext.
        if (context instanceof NullContext) {
            return;
        }
        this.parent = context.getCurEntry();
        if (parent != null) {
            ((CtEntry) parent).child = this;
        }
        context.setCurEntry(this);
    }
}
复制代码

7、规则校验

CtSph.entryWithPriority的第五步,执行ProcessorSlotChain的entry方法,如果规则校验未通过,这里会抛出BlockException,如果规则校验通过,这里会返回第四步得到的CtEntry。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    // 1. 获取当前线程Context
    // 2. 如果用户没有主动创建Context,使用默认上下文sentinel_default_context
    // 3. 获取Slot链
    // 4. 构造CtEntry,构造时将这个Entry接入Context中的Entry链表尾部
    try {
        // 5. 执行所有规则校验
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } catch (Throwable e1) {
        RecordLog.info("Sentinel unexpected exception", e1);
    }
    return e;
}
复制代码

ProcessorSlotChain的实现类是DefaultProcessorSlotChain。

DefaultProcessorSlotChain按照SPI机制加载了很多ProcessorSlot插槽,通过first.transformEntry方法,执行第一个ProcessorSlot。

public class DefaultProcessorSlotChain extends ProcessorSlotChain {

    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 entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
        throws Throwable {
        first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
    }
}
复制代码

所有ProcessorSlot都继承了AbstractLinkedProcessorSlot抽象类,形成链表,每次当前Slot执行完自己的职责后(责任链),会调用抽象类中的fireEntry方法,执行下一个Slot的entry方法。

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
      // 下一个slot
    private AbstractLinkedProcessorSlot<?> next = null;
    // 执行下一个slot的entry方法
    @Override
    public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        if (next != null) {
            next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
        }
    }
    // 强转泛型,执行entry方法
    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);
    }
}
复制代码

如NodeSelectorSlot执行完自己的业务后,调用fireEntry执行下一个Slot。

@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        // 1. 执行自己的逻辑
          // ...
          // 2. 执行下一个Slot
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
}
复制代码

目前总共有8个重要的Slot,按照Slot排列顺序如下,前三个提供数据支撑,后五个负责规则校验(抛出BlockException):

  1. NodeSelectorSlot:构建资源(Resource)的路径(DefaultNode),用树的结构存储。
  2. ClusterBuilderSlot:构建ClusterNode,用于记录资源维度的统计信息。
  3. StatisticSlot:使用Node记录指标信息,如RT、Pass/Block Count,为后续规则校验提供数据支撑。
  4. AuthoritySlot:授权规则校验
  5. SystemSlot:系统规则校验
  6. ParamFlowSlot:热点参数流控规则校验
  7. FlowSlot:流控规则校验
  8. DegradeSlot:降级规则校验

具体每个Slot的源码放到下一章再详述。

8、exit

Sph.entry执行规则校验,会返回用户Entry,接着用户代码执行业务逻辑,当业务逻辑处理完成后,用户代码会在finally块中调用Entry.exit方法。

Entry entry = null;
try {
    // 执行规则校验,返回CtEntry
    entry = SphU.entry(KEY);
    // ...业务逻辑
} finally {
    // Entry退出
    if (entry != null) {
        entry.exit();
    }
}
复制代码

此外如果发生BlockException,CtSph会主动调用entry.exit方法。

// CtSph.java
private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
    throws BlockException {
    // ...
    try {
        // 5. 执行所有规则校验
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
    } catch (BlockException e1) {
        e.exit(count, args);
        throw e1;
    } 
}
复制代码

Entry.exit方法将当前Entry从上下文中移除,最终会调用实现类CtEntry的exitForContext方法。

// CtEntry.java

// 上一个入口Entry
protected Entry parent = null;
// 下一个Entry
protected Entry child = null;
// Slot插槽
protected ProcessorSlot<Object> chain;
// 上下文
protected Context context;
protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;

@Override
protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException {
  exitForContext(context, count, args);
  return parent;
}

protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
    if (context != null) { // 如果Entry已经调用过一次退出,这里不会再次退出
        // NullContext忽略
        if (context instanceof NullContext) {
            return;
        }

        if (context.getCurEntry() != this) {
            // 错误的entry退出,清空context中的所有entry并抛出异常
            String curEntryNameInContext = context.getCurEntry() == null ? null
                : context.getCurEntry().getResourceWrapper().getName();
            CtEntry e = (CtEntry) context.getCurEntry();
            while (e != null) {
                e.exit(count, args);
                e = (CtEntry) e.parent;
            }
            String errorMessage = String.format("The order of entry exit can't be paired with the order of entry"
                    + ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext,
                resourceWrapper.getName());
            throw new ErrorEntryFreeException(errorMessage);
        } else {
            // 正常的entry退出
            // 1. 执行所有Slot的exit方法
            if (chain != null) {
                chain.exit(context, resourceWrapper, count, args);
            }
            // 2. 执行所有exitHandlers(降级规则会用到)
            callExitHandlersAndCleanUp(context);

            // 3. context中entry链表移除当前entry
            context.setCurEntry(parent);
            if (parent != null) {
                ((CtEntry) parent).child = null;
            }
            if (parent == null) {
                if (ContextUtil.isDefaultContext(context)) {
                    ContextUtil.exit();
                }
            }
            // 4. 当前entry.context = null,防止重复exit
            clearEntryContext();
        }
    }
}
复制代码

如果退出Entry是当前上下文的最后一个Entry,才会执行正常业务逻辑,否则会抛出ErrorEntryFreeException

退出Entry分为四步:

  1. DefaultProcessorSlotChain从前到后执行所有ProcessorSlot
public class DefaultProcessorSlotChain extends ProcessorSlotChain {

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

}
复制代码
  1. callExitHandlersAndCleanUp执行当前Entry上所有的exitHandlers,这主要是为了Degrade降级规则的断路器服务,后续再说
// CtEntry.java
protected LinkedList<BiConsumer<Context, Entry>> exitHandlers;
private void callExitHandlersAndCleanUp(Context ctx) {
    if (exitHandlers != null && !exitHandlers.isEmpty()) {
        for (BiConsumer<Context, Entry> handler : this.exitHandlers) {
            try {
                handler.accept(ctx, this);
            } catch (Exception e) {
                RecordLog.warn("Error occurred when invoking entry exit handler, current entry: "
                    + resourceWrapper.getName(), e);
            }
        }
        exitHandlers = null;
    }
}
复制代码
  1. 链表操作,将当前CtEntry从Context上下文的Entry链表中移除(调用链表如何构成,见第6节CtEntry的构造方法)
  2. clearEntryContext,设置当前CtEntry关联的Context上下文为null
// CtEntry.java
protected void clearEntryContext() {
    this.context = null;
}
复制代码

总结

本章阅读了Sph.entry和Entry.exit的执行流程。

Sph.entry

用户代码在执行之前,可以通过Sph.entry进行规则校验,如果校验通过了,会继续执行,否则会抛出BlockException。

entry正向流程.png

Sph.entry分为以下几步:

  1. 可选,用户代码可以通过InternalContextUtil.enter方法,创建非默认Context,同时会创建Context对应的EntranceNode;
  2. 进入CtSph.entry方法,创建资源名称对应Resource对象;
  3. 进入CtSph.entryWithPriority方法,首先获取ThreadLocal中当前线程对应的Context,如果不存在,使用默认的sentinel_default_context上下文;
  4. 通过SPI机制加载ProcessorSlot链表,同一个Resource名称,对应同一个ProcessorSlot链表实例;
  5. 构造CtEntry实例,构造方法内将本Entry实例加入了Context的Entry链表;
  6. 执行所有ProcessorSlot.entry,执行规则校验;
  7. 如果规则校验没通过,抛出BlockException;否则正常执行用户代码;

Entry.exit

用户代码执行完毕后,需要通过Entry.exit释放资源。

exit逆向流程.png

Entry.exit分为以下几步:

  1. 执行所有ProcessorSlot.exit;
  2. 执行所有Entry.exitHandlers,只有降级规则会用到;
  3. 解除Entry与Context的关系:Context的Entry链表中移除当前Entry,当前Entry对应的Context置为null;