• 20SentinelArchitecture


    1. SPI

    摘自:https://www.jianshu.com/p/3a3edbcd8f24

    SPI 全称为 Service Provider Interface,是一种服务发现机制。它通过在 classPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类。

    这一机制为很多框架扩展提供了可能,比如在 Dubbo、JDBC 中都使用到了 SPI 机制。我们先通过一个很简单的例子来看下它是怎么用的。

    1.1 demo

    a. 测试代码

    首先,我们需要定义一个接口:SPIService

    package com.viewscenes.netsupervisor.spi;
    
    public interface SPIService {
        void execute();
    }
    

    然后,定义两个实现类,没别的意思,只输入一句话。

    package com.viewscenes.netsupervisor.spi;
    public class SpiImpl1 implements SPIService{
        public void execute() {
            System.out.println("SpiImpl1.execute()");
        }
    }
    ---------------------- 分割线 ----------------------
    package com.viewscenes.netsupervisor.spi;
    public class SpiImpl2 implements SPIService{
        public void execute() {
            System.out.println("SpiImpl2.execute()");
        }
    }
    

    最后呢,要在 classPath 路径下配置添加一个文件。文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。

    文件路径如下:

    内容就是实现类的全限定类名:

    com.viewscenes.netsupervisor.spi.SpiImpl1
    com.viewscenes.netsupervisor.spi.SpiImpl2
    

    然后我们就可以通过 ServiceLoader.load 或者 Service.providers 方法拿到实现类的实例。其中,Service.providers 包位于 sun.misc.Service,而 ServiceLoader.load 包位于 java.util.ServiceLoader

    public class SPITest {
        public static void main(String[] args) {
            Iterator<SPIService> providers = Service.providers(SPIService.class);
            ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
    
            while(providers.hasNext()) {
                SPIService ser = providers.next();
                ser.execute();
            }
            System.out.println("--------------------------------");
            Iterator<SPIService> iterator = load.iterator();
            while(iterator.hasNext()) {
                SPIService ser = iterator.next();
                ser.execute();
            }
        }
    }
    

    两种方式的输出结果是一致的:

    SpiImpl1.execute()
    SpiImpl2.execute()
    --------------------------------
    SpiImpl1.execute()
    SpiImpl2.execute()
    

    b. 源码分析

    我们看到一个位于 sun.misc 包,一个位于 java.util 包,sun 包下的源码看不到。我们就以 ServiceLoader.load 为例,通过源码看看它里面到底怎么做的。

    (1)ServiceLoader 类结构

    public final class ServiceLoader<S> implements Iterable<S>
        // 配置文件的路径
        private static final String PREFIX = "META-INF/services/";
        // 加载的服务类或接口
        private final Class<S> service;
        // 已加载的服务类集合
        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
        // 类加载器
        private final ClassLoader loader;
        // 内部类,真正加载服务类
        private LazyIterator lookupIterator;
    }
    

    (2)load 方法

    public final class ServiceLoader<S> implements Iterable<S>
        private ServiceLoader(Class<S> svc, ClassLoader cl) {
            // 要加载的接口
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            // 类加载器
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            // 访问控制器
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            // 先清空
            providers.clear();
            // 实例化内部类
            LazyIterator lookupIterator = new LazyIterator(service, loader);
        }
    }
    

    (3)查找实现类

    查找实现类和创建实现类的过程,都在 LazyIterator 完成。当我们调用 iterator.hasNextiterator.next 方法的时候,实际上调用的都是 LazyIterator 的相应方法。

    public Iterator<S> iterator() {
        return new Iterator<S>() {
            public boolean hasNext() {
                return lookupIterator.hasNext();
            }
            public S next() {
                return lookupIterator.next();
            }
            .......
        };
    }
    

    所以,我们重点关注 lookupIterator.hasNext() 方法,它最终会调用到 hasNextService

    private class LazyIterator implements Iterator<S>{
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;
        private boolean hasNextService() {
            // 第 2 次调用的时候,已经解析完成了,直接返回
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                // META-INF/services/<接口的全限定类名>
                // META-INF/services/com.viewscenes.netsupervisor.spi.SPIService
                String fullName = PREFIX + service.getName();
                // 将文件路径转成 URL 对象
                configs = loader.getResources(fullName);
            }
            while ((pending == null) || !pending.hasNext()) {
                // 解析 URL 文件对象、读取内容、最后返回
                pending = parse(service, configs.nextElement());
            }
            // 拿到第一个实现类的类名
            nextName = pending.next();
            return true;
        }
    }
    

    (4)创建实例

    当然,调用 next 方法的时候,实际调用到的是 lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。

    private class LazyIterator implements Iterator<S>{
        private S nextService() {
            // 全限定类名
            String cn = nextName;
            nextName = null;
            // 创建类的 Class 对象
            Class<?> c = Class.forName(cn, false, loader);
            // 通过 newInstance 实例化
            S p = service.cast(c.newInstance());
            // 放入集合,返回实例
            providers.put(cn, p);
            return p;
        }
    }
    

    1.2 JDBC

    我们开头说,SPI 机制为很多框架的扩展提供了可能,其实 JDBC 就应用到了这一机制。回忆一下 JDBC 获取数据库连接的过程。在早期版本中,需要先设置数据库驱动的连接,再通过 DriverManager.getConnection 获取一个 Connection。

    String url = "jdbc:mysql:///consult?serverTimezone=UTC";
    String user = "root";
    String password = "root";
    
    Class.forName("com.mysql.jdbc.Driver");
    Connection connection = DriverManager.getConnection(url, user, password);
    

    它是怎么分辨是哪种数据库的呢?答案就在 SPI。

    a. 加载

    我们把目光回到 DriverManager 类,它在静态代码块里面做了一件比较重要的事。很明显,它已经通过 SPI 机制, 把数据库驱动连接初始化了。

    public class DriverManager {
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
    }
    

    具体过程还得看 loadInitialDrivers,它在里面查找的是 Driver 接口的服务类,所以它的文件路径就是:META-INF/services/java.sql.Driver

    public class DriverManager {
        private static void loadInitialDrivers() {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    // 很明显,它要加载 Driver 接口的服务类,Driver接口的包为 java.sql.Driver
                    // 所以它要找的就是 META-INF/services/java.sql.Driver 文件
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
                    try{
                        // 查到之后创建对象
                        while(driversIterator.hasNext()) {
                            driversIterator.next();
                        }
                    } catch(Throwable t) {
                        // do nothing
                    }
                    return null;
                }
            });
        }
    }
    

    那么,这个文件哪里有呢?我们来看 MySQL 的 jar 包,就是如下这个文件,文件内容为:com.mysql.cj.jdbc.Driver

    b. 创建实例

    上一步已经找到了 MySQL 中的 com.mysql.cj.jdbc.Driver 全限定类名,当调用 next 方法时,就会创建这个类的实例。它就完成了一件事,向 DriverManager 注册自身的实例。

    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        static {
            try {
                // 调用 DriverManager 类的注册方法,向 registeredDrivers 集合中加入实例
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        public Driver() throws SQLException {
            // Required for Class.forName().newInstance()
        }
    }
    

    c. 创建 Connection

    DriverManager.getConnection() 方法就是创建连接的地方,它通过循环已注册的数据库驱动程序,调用其 connect 方法,获取连接并返回。

    private static Connection getConnection(
            String url, java.util.Properties info, Class<?> caller) throws SQLException {
        // registeredDrivers 中就包含 com.mysql.cj.jdbc.Driver实例
        for (DriverInfo aDriver : registeredDrivers) {
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    // 调用 connect 方法创建连接
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } else {
                println(" skipping: " + aDriver.getClass().getName());
            }
        }
    }
    

    d. 再扩展

    既然我们知道 JDBC 是这样创建数据库连接的,我们能不能再扩展一下呢?如果我们自己也创建一个 java.sql.Driver 文件,自定义实现类 MyDriver,那么,在获取连接的前后就可以动态修改一些信息。

    还是先在项目 classPath 下创建文件,文件内容为自定义驱动类 com.viewscenes.netsupervisor.spi.MyDriver

    我们的 MyDriver 实现类,继承自 MySQL 中的 NonRegisteringDriver,还要实现 java.sql.Driver 接口。这样,在调用 connect 方法的时候,就会调用到此类,但实际创建的过程还靠 MySQL 完成。

    package com.viewscenes.netsupervisor.spi
    
    public class MyDriver extends NonRegisteringDriver implements Driver {
        static {
            try {
                java.sql.DriverManager.registerDriver(new MyDriver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        public MyDriver() throws SQLException {}
    
        public Connection connect(String url, Properties info) throws SQLException {
            System.out.println("准备创建数据库连接#url:" + url);
            System.out.println("JDBC配置信息:" + info);
            info.setProperty("user", "root");
            Connection connection =  super.connect(url, info);
            System.out.println("数据库连接创建完成!" + connection.toString());
            return connection;
        }
    }
    --------------------输出结果---------------------
    准备创建数据库连接#url:jdbc:mysql:///consult?serverTimezone=UTC
    JDBC配置信息:{user=root, password=root}
    数据库连接创建完成!com.mysql.cj.jdbc.ConnectionImpl@7cf10a6f
    

    2. 基本原理

    a. Slot

    https://sentinelguard.io/zh-cn/docs/basic-implementation.html

    在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:

    NodeSelectorSlot
        负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;
    ClusterBuilderSlot
        则用于存储资源的统计信息以及调用者信息,如该资源的 RT/QPS/threadCount/exceptionCount 等,
        这些信息将用作为多维度限流、降级的依据;
    StatisticSlot
        则用于记录、统计不同纬度的 runtime 指标监控信息;
    ························································································
    ParamFlowSlot
        用于【热点参数限流】;
    FlowSlot
        则用于根据预设的限流规则以及前面 slot 统计的状态,来进行【流量控制】;
    AuthoritySlot
        则根据配置的黑白名单和调用来源信息,来做【黑白名单控制】;
    DegradeSlot
        则通过统计信息以及预设的规则,来做【熔断降级】;
    SystemSlot
        则通过系统的状态,例如 load1 等,来控制【总的入口流量】;
    

    Sentinel 的核心骨架是 ProcessorSlotChain。其将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能组合在一起(限流、降级、系统保护)。系统会为每个资源创建一套 SlotChain。

    Sentinel 将 ProcessorSlot 作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder 作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。

    b. Context

    Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。Context 维持着入口节点(entranceNode)、本次调用链路的 curNode、调用来源(origin)等信息。Context 名称即为调用链路入口名称。

    Context 维持的方式:通过 ThreadLocal 传递,只有在入口 enter 的时候生效。由于 Context 是通过 ThreadLocal 传递的,因此对于异步调用链路,线程切换时会丢掉 Context,因此需要手动通过 contextUtil.runOnContext(context, f) 来变换 context。

    Context 是对资源操作的上下文,每个资源操作必须属于一个 Context。如果代码中没有指定 Context,则会创建一个 namesentinel_default_context 的默认 Context。一个 Context 生命周期中可以包含多个资源操作。Context 生命周期中的最后一个资源在 exit() 时会清理该 Conetxt,这也就意味着这个 Context 生命周期结束了。

    /**
     * The fundamental Sentinel API for recording statistics and performing rule
     * checking for resources.
     *
     * Conceptually, physical or logical resource that need protection should be
     * surrounded by an entry. The requests to this resource will be blocked if any
     * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked,
     * a {@link BlockException} will be thrown.
     *
     * To configure the criteria, we can use `XxxRuleManager.loadRules()` to load rules.
     *
     * Following code is an example, {@code "abc"} represent a unique name for the
     * protected resource:
     *
     *  public void foo() {
     *     Entry entry = null;
     *     try {
     *        entry = SphU.entry("abc");
     *        // resource that need protection
     *     } catch (BlockException blockException) {
     *         // when goes there, it is blocked
     *         // add blocked handle logic here
     *     } catch (Throwable bizException) {
     *         // business exception
     *         Tracer.trace(bizException);
     *     } finally {
     *         // ensure finally be executed
     *         if (entry != null){
     *             entry.exit();
     *         }
     *     }
     *  }
     *
     * Make sure {@code SphU.entry()} and {@link Entry#exit()} be paired in the same thread,
     * otherwise {@link ErrorEntryFreeException} will be thrown.
     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * 上面这段注释来自 com.alibaba.csp.sentinel.SphU(都是基本单词能读懂)
     */
    
    
    // 创建一个来自于 appA 访问的 Context, entranceOne 是 Context 的 name
    ContextUtil.enter("entranceOne", "appA");
    // Entry 是用来表示是资源是否可以继续执行一个凭证,可以理解为 token。
    Entry resource1 = null;
    Entry resource2 = null;
    try {
        // 获取资源 resource1 的 entry
        // 使用 SphU.entry() 会返回一个 entry,如果抛出 BlockException,说明资源被保护了。
        resource1 = SphU.entry("resource1");
        // => 代码能走到这里,说明当前对资源 resource1 的请求通过了流控
    
        // ===== 对资源resource1的相关业务处理 =====
    
        // 获取资源 resource2 的 entry
        // 使用 SphU.entry() 会返回一个 entry,如果抛出 BlockException,说明资源被保护了。
        resource2 = SphU.entry("resource2");
        // => 代码能走到这里,说明当前对资源 resource2 的请求通过了流控
    
        // ===== 对资源 resource2 的相关业务处理 =====
    
    } catch (BlockException e) {
        // 代码能走到这里,说明请求被限流,这里执行降级处理...
    } finally {
        if (resource1 != null) {
            resource1.exit();
        }
        if (resource2 != null) {
            resource2.exit();
        }
    }
    // 释放 Context
    ContextUtil.exit();
    
    // ===== 使用 SphO.entry() 时,资源被保护了会返回 false,反之 true。=====
    
    // 创建另一个来自于 appA 访问的 Context,entranceTwo 是 Context 的 name
    ContextUtil.enter("entranceTwo", "appA");
    // Entry 是用来表示是资源是否可以继续执行一个凭证,可以理解为 token。
    Entry resource3 = null;
    try {
        // 获取资源 resource2 的 entry
        // 使用 SphU.entry() 会返回一个 entry,如果抛出 BlockException,说明资源被保护了。
        resource2 = SphU.entry("resource2");
        // => 代码能走到这里,说明当前对资源 resource2 的请求通过了流控
    
        // ===== 对资源 resource2 的相关业务处理 =====
    
        // 获取资源 resource3 的 entry
        // 使用 SphU.entry() 会返回一个 entry,如果抛出 BlockException,说明资源被保护了。
        resource3 = SphU.entry("resource3");
        // => 代码能走到这里,说明当前对资源 resource3 的请求通过了流控
    
        // ===== 对资源 resource3 的相关业务处理 =====
    
    } catch (BlockException e) {
        // 代码能走到这里,说明请求被限流,这里执行降级处理...
    } finally {
        if (resource2 != null) {
            resource2.exit();
        }
        if (resource3 != null) {
            resource3.exit();
        }
    }
    // 释放Context
    ContextUtil.exit();
    

    c. Node

    结合上述示例代码观察 Node 间的关系:

    • Node:用于完成数据统计的接口;
    • StatisticNode:统计节点,是 Node 接口的实现类,用于完成数据统计;
    • EntranceNode:入口节点,一个 Context 会有一个入口节点,用于统计当前 Context 的总体流量数据;
    • DefaultNode:默认节点,用于统计一个资源在当前 Context 中的流量数据;
    • ClusterNode:集群节点,用于统计一个资源在所有 Context 中的总体流量数据。

    类的结构图:

    3. 源码解析 basic

    SentinelAutoConfiguration

    @Bean
    @ConditionalOnMissingBean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
    

    SentinelResourceAspect

    /**
     * Aspect for methods with {@link SentinelResource} annotation.
     */
    @Aspect
    public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
    
      /**
       * 指定切入点为 @SentinelResource 注解
       */
      @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
      public void sentinelResourceAnnotationPointcut() {}
    
      /**
       * 指定此为环绕通知 around advice
       * @param pjp
       * @return
       * @throws Throwable
       */
      @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");
        }
        String resourceName = getResourceName(annotation.value(), originMethod);
        EntryType entryType = annotation.entryType();
        int resourceType = annotation.resourceType();
        Entry entry = null;
        try {
          // =====> [1] 要织入的、增强的功能
          entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
          // 调用目标方法
          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())) {
            traceException(ex);
            return handleFallback(pjp, annotation, ex);
          }
          // No fallback function can handle the exception, so throw it out.
          throw ex;
        } finally {
          if (entry != null) {
            entry.exit(1, pjp.getArgs());
          }
        }
      }
    }
    

    3.1 SphU

    /**
     * Record statistics and perform rule checking for the given resource.
     *
     * @param name         the unique name for the protected resource
     * @param trafficType  the traffic type (inbound, outbound or internal). This is used
     *                     to mark whether it can be blocked when the system is unstable,
     *                     only inbound traffic could be blocked by {@link SystemRule}
     * @param resourceType classification of the resource (e.g. Web or RPC)
     * @param args         args for parameter flow control or customized slots
     * @return the {@link Entry} of this invocation (used for mark the invocation complete
     *                     and get context data)
     * @throws BlockException if the block criteria is met
     *                     (e.g. metric exceeded the threshold of any rules)
     * @since 1.7.0
     */
    public static Entry entry(String name, int resourceType, EntryType trafficType,
            Object[] args) throws BlockException {
        // =====> [2] 注意 param4 = 1!
        return Env.sph.entryWithType(name, resourceType, trafficType, 1, args);
    }
    

    3.2 CtSph*

    @Override
    public Entry entryWithType(String name, int resourceType, EntryType entryType,
            int count, Object[] args) throws BlockException {
        // =====> [3] count: 表示当前请求可以增加多少个计数
        // Param5#prioritized = false
        return 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 resource = new StringResourceWrapper(name, entryType, resourceType);
        // =====> [4] 返回一个资源操作对象 entry
        // prioritized=true   则表示当前访问必须等待“根据其优先级计算出的时间”后才可通过
        // prioritized=false  则当前请求无需等待
        return entryWithPriority(resource, count, prioritized, args);
    }
    
    /**
     * 核心方法!
     * @param resourceWrapper  资源实例
     * @param count            默认值为 1
     * @param prioritized      默认值为 false
     * @param args
     * @return
     * @throws BlockException
     */
    private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count,
            boolean prioritized, Object... args) throws BlockException {
    
        // =====> [5] 从 ThreadLocal 中获取 context
        // 即一个请求会占用一个线程,一个线程会绑定一个 context
        Context context = ContextUtil.getContext();
    
        // 若 context 是 NullContext 类型,则表示当前系统中的 context 数量已经超出的阈值,
        // 即访问请求的数量已经超出了阈值。此时直接返回一个无需做规则检测的资源操作对象。
        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);
        }
    
        // =====> [6] 若当前线程中没有绑定 context,则创建 default context 并将其放入到 Thread 中
        // String CONTEXT_DEFAULT_NAME = "sentinel_default_context";
        if (context == null) {
            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);
        }
    
        // =====> [7] 查找 SlotChain
        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.
         */
        // 若没有找到 chain,则意味着 chain 数量超出了阈值,则直接返回一个无需做规则检测的资源操作对象
        if (chain == null) {
            return new CtEntry(resourceWrapper, null, context);
        }
    
        // 创建一个资源操作对象
        Entry e = new CtEntry(resourceWrapper, chain, context);
        try {
            // =====> [8] 对资源进行操作
            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;
    }
    
    /**
     * =====> [7] 延伸
     *
     * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
     * be created if the resource doesn't relate one.
     *
     * <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
     * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.<p/>
     *
     * <p>
     * Note that total {@link ProcessorSlot} count must not exceed
     * {@link Constants#MAX_SLOT_CHAIN_SIZE}, otherwise null will return.
     * </p>
     *
     * @param resourceWrapper target resource
     * @return {@link ProcessorSlotChain} of the resource
     */
    ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
      // 从缓存 map 中获取当前资源的 SlotChain
      // key 为 resource,value 为其相关的 SlotChain
      ProcessorSlotChain chain = chainMap.get(resourceWrapper);
      // [DCL] 若缓存中没有相关的 SlotChain,则创建一个并放入到缓存
      if (chain == null) {
        synchronized (LOCK) {
          chain = chainMap.get(resourceWrapper);
          if (chain == null) {
            // Entry size limit.
            // 缓存 map 的 size >= chain 数量阈值,则直接返回 null,不再创建新的 chain
            if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
              return null;
            }
    
            // =====> [7] 延伸:创建新的 chain
            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] 延伸
     * This class is used for skip context name checking.
     */
    private final static class InternalContextUtil extends ContextUtil {
        static Context internalEnter(String name) {
            // =====> origin = ""
            return trueEnter(name, "");
        }
    
        static Context internalEnter(String name, String origin) {
            return trueEnter(name, origin);
        }
    }
    

    3.3 ContextUtil

    /**
     * Store the context in ThreadLocal for easy access.
     */
    private static ThreadLocal<Context> contextHolder = new ThreadLocal<>();
    
    /**
     * =====> [5] Get {@link Context} of current thread.
     *
     * @return context of current thread. Null value will be return if current
     * thread does't have context.
     */
    public static Context getContext() {
        return contextHolder.get();
    }
    
    // =====> [6] 延伸
    protected static Context trueEnter(String name, String origin) {
      // 尝试着从 ThreadLocal 中获取 Context
      Context context = contextHolder.get();
      // 若 ThreadLocal 中没有 context,则尝试着从缓存 map 中获取
      if (context == null) {
        // key 为 context 名称,value 为 EntranceNode (DefaultNode extends EntranceNode)
        Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
        // 获取 EntranceNode —— 双重检测锁DCL —— 为了防止并发创建
        DefaultNode node = localCacheNameMap.get(name);
        if (node == null) {
          // 若缓存 map 的 size 大于 context 数量的最大阈值,则直接返回 NULL_CONTEXT
          if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
            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 {
                  // 创建一个 EntranceNode
                  node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                  // 将其添加到 ROOT
                  Constants.ROOT.addChild(node);
    
                  // 将新建 node 写入到缓存 map
                  // 为了防止“迭代稳定性问题” —— iterate stable —— 对于共享集合的写操作
                  Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size()+1);
                  newMap.putAll(contextNameNodeMap);
                  newMap.put(name, node);
                  contextNameNodeMap = newMap;
                }
              }
            } finally {
              LOCK.unlock();
            }
          }
        }
        // 将 context#name 与 entranceNode 封装为 context
        context = new Context(node, name);
        // 初始化 context 的来源
        context.setOrigin(origin);
        // 将 context 写入到 ThreadLocal
        contextHolder.set(context);
      }
      return context;
    }
    

    3.4 SlotChainProvider

    // =====> [7] 延伸
    
    public final class SlotChainProvider {
    
      private SlotChainProvider() {}
    
      private static volatile SlotChainBuilder slotChainBuilder = null;
    
      /**
       * The load and pick process is not thread-safe, but it's okay
       * since the method should be only invoked.
       * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock.
       *
       * @return new created slot chain
       */
      public static ProcessorSlotChain newSlotChain() {
        // 若 builder 不为 null,则直接使用 builder 构建一个 chain,否则先创建一个 builder
        if (slotChainBuilder != null) {
          return slotChainBuilder.build();
        }
    
        // =====> [7.1] 通过 SPI 方式创建一个 Builder
        slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
    
        // 若通过 SPI 方式未能创建 Builder,则手工 new 一个 DefaultSlotChainBuilder
        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());
        }
        // =====> [7.2] 通过 Builder 构建一个 chain
        return slotChainBuilder.build();
      }
    }
    

    3.5 DefaultSlotChainBuilder

    /**
     * Builder for a default {@link ProcessorSlotChain}.
     */
    @Spi(isDefault = true)
    public class DefaultSlotChainBuilder implements SlotChainBuilder {
    
      @Override
      public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
    
        // 通过 SPI 方式构建 Slot
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
          if (!(slot instanceof AbstractLinkedProcessorSlot)) {
            RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName()
                + ") is not an instance of AbstractLinkedProcessorSlot, "
                + "can't be added intoProcessorSlotChain");
            continue;
          }
    
          chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }
    
        return chain;
      }
    }
    

    [7.2] 全局查找名为 com.alibaba.csp.sentinel.slotchain.ProcessorSlot 的文件:

    • sentinel-core
      # Sentinel default ProcessorSlots
      com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
      com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
      com.alibaba.csp.sentinel.slots.logger.LogSlot
      com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
      com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
      com.alibaba.csp.sentinel.slots.system.SystemSlot
      com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
      com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
      
    • sentinel-parameter-flow-control
      com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot
      
    • sentinel-api-gateway-adapter-common
      com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot
      

    Constants:

    public final class Constants {
    
        public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.8.1");
    
        public final static int MAX_CONTEXT_NAME_SIZE = 2000;
        public final static int MAX_SLOT_CHAIN_SIZE = 6000;
    
        public final static String ROOT_ID = "machine-root";
        public final static String CONTEXT_DEFAULT_NAME = "sentinel_default_context";
    
        /**
         * A virtual resource identifier for total inbound statistics (since 1.5.0).
         */
        public final static String TOTAL_IN_RESOURCE_NAME = "__total_inbound_traffic__";
    
        /**
         * A virtual resource identifier for cpu usage statistics (since 1.6.1).
         */
        public final static String CPU_USAGE_RESOURCE_NAME = "__cpu_usage__";
    
        /**
         * A virtual resource identifier for system load statistics (since 1.6.1).
         */
        public final static String SYSTEM_LOAD_RESOURCE_NAME = "__system_load__";
    
        /**
         * Global ROOT statistic node that represents the universal parent node.
         */
        public final static DefaultNode ROOT = new EntranceNode(
                    new StringResourceWrapper(ROOT_ID, EntryType.IN),
                    new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON));
    
        /**
         * Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking.
         */
        public final static ClusterNode ENTRY_NODE = new ClusterNode(
                            TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON);
    
        /**
         * The global switch for Sentinel.
         */
        public static volatile boolean ON = true;
    
        /**
         * Order of default processor slots (越小优先级越高)
         */
        public static final int ORDER_NODE_SELECTOR_SLOT = -10000;
        public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000;
        public static final int ORDER_LOG_SLOT = -8000;
        public static final int ORDER_STATISTIC_SLOT = -7000;
        public static final int ORDER_AUTHORITY_SLOT = -6000;
        public static final int ORDER_SYSTEM_SLOT = -5000;
        public static final int ORDER_FLOW_SLOT = -2000;
        public static final int ORDER_DEGRADE_SLOT = -1000;
    
        private Constants() {}
    }
    

    3.6 DefaultProcessorSlotChain

    /**
     * 这是一个单向链表,默认包含 1 个节点,且有两个指针 first 与 end 同时指向这个节点
     */
    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 exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
          super.fireExit(context, resourceWrapper, count, args);
        }
    
      };
    
      AbstractLinkedProcessorSlot<?> end = first;
    
      @Override
      public void addFirst(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        protocolProcessor.setNext(first.getNext());
        first.setNext(protocolProcessor);
        if (end == first) {
          end = protocolProcessor;
        }
      }
    
      @Override
      public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {
        end.setNext(protocolProcessor);
        end = protocolProcessor;
      }
    
      /**
       * Same as {@link #addLast(AbstractLinkedProcessorSlot)}.
       *
       * @param next processor to be added.
       */
      @Override
      public void setNext(AbstractLinkedProcessorSlot<?> next) {
        addLast(next);
      }
    
      @Override
      public AbstractLinkedProcessorSlot<?> getNext() {
        return first.getNext();
      }
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, Object t,
              int count, boolean prioritized, Object... args) throws Throwable {
        // =====> [8] 转向下一个节点
        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);
      }
    
    }
    

    3.7 AbstractLinkedProcessorSlot

    // =====> [8] 延伸
    public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    
      /**
       * 声明了一个同类型的变量,其可以指向下一个 Slot 节点
       */
      private AbstractLinkedProcessorSlot<?> next = null;
    
      @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);
        }
      }
    
      @SuppressWarnings("unchecked")
      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);
      }
    
      @Override
      public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        if (next != null) {
          next.exit(context, resourceWrapper, count, args);
        }
      }
    
      public AbstractLinkedProcessorSlot<?> getNext() {
        return next;
      }
    
      public void setNext(AbstractLinkedProcessorSlot<?> next) {
        this.next = next;
      }
    
    }
    

    4. 源码解析 slotChain

    4.1 XxxSlot

    a. NodeSelectorSlot

    /**
     * This class will try to build the calling traces via
     * - adding a new {@link DefaultNode} if needed as the last child in the context.
     *   The context's last node is the current node or the parent node of the context.
     * - setting itself to the context current node.
     *
     * It works as follow:
     *
     *     ContextUtil.enter("entrance1", "appA");
     *     Entry nodeA = SphU.entry("nodeA");
     *     if (nodeA != null) {
     *         nodeA.exit();
     *     }
     *     ContextUtil.exit();
     *
     *
     * Above code will generate the following invocation structure in memory:
     *
     *
     *              machine-root
     *                  /
     *                 /
     *           EntranceNode1
     *               /
     *              /
     *        DefaultNode(nodeA)- - - - - -> ClusterNode(nodeA);
     *
     *
     * Here the {@link EntranceNode} represents "entrance1" given by
     * {@code ContextUtil.enter("entrance1", "appA")}.
     *
     * Both DefaultNode(nodeA) and ClusterNode(nodeA) holds statistics of "nodeA",
     * which is given by {@code SphU.entry("nodeA")}
     *
     * The {@link ClusterNode} is uniquely identified by the ResourceId; the {@link DefaultNode}
     * is identified by both the resource id and {@link Context}. In other words, one resource
     * id will generate multiple {@link DefaultNode} for each distinct context, but only one
     * {@link ClusterNode}.
     *
     * the following code shows one resource id in two different context:
     *
     *    ContextUtil.enter("entrance1", "appA");
     *    Entry nodeA = SphU.entry("nodeA");
     *    if (nodeA != null) {
     *        nodeA.exit();
     *    }
     *    ContextUtil.exit();
     *
     *    ContextUtil.enter("entrance2", "appA");
     *    nodeA = SphU.entry("nodeA");
     *    if (nodeA != null) {
     *        nodeA.exit();
     *    }
     *    ContextUtil.exit();
     *
     *
     * Above code will generate the following invocation structure in memory:
     *
     *
     *                  machine-root
     *                  /         \
     *                 /           \
     *         EntranceNode1   EntranceNode2
     *               /               \
     *              /                 \
     *      DefaultNode(nodeA)   DefaultNode(nodeA)
     *             |                    |
     *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
     *
     *
     * As we can see, two {@link DefaultNode} are created for "nodeA" in two context,
     * but only one {@link ClusterNode} is created.
     *
     * We can also check this structure by calling: <br/>
     * {@code curl http://localhost:8719/tree?type=root}
     *
     */
    @Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
    public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
    
    
     /**
      * {@link DefaultNode}s of the same resource in different context.
      */
     private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
    
     @Override
     public void entry(Context context, ResourceWrapper resourceWrapper, Object obj,
            int count, boolean prioritized, Object... args) throws Throwable {
      /*
       * It's interesting that we use context name rather resource name as the map key.
       *
       * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share
       * the same {@link ProcessorSlotChain} globally, no matter in which context. So if
       * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, Object...)},
       * the resource name must be same but context name may not.
       *
       * If we use {@link com.alibaba.csp.sentinel.SphU#entry(String resource)} to enter
       * same resource in different context, using context name as map key can distinguish
       * the same resource. In this case, multiple {@link DefaultNode}s will be created of
       * the same resource name, for every distinct context (different context name) each.
       *
       * Consider another question. One resource may have multiple {@link DefaultNode},
       * so what is the fastest way to get total statistics of the same resource?
       * The answer is all {@link DefaultNode}s with same resource name share one
       * {@link ClusterNode}. See {@link ClusterBuilderSlot} for detail.
       */
    
      // 从缓存中获取 DefaultNode
      DefaultNode node = map.get(context.getName());
      // DCL
      if (node == null) {
        synchronized (this) {
          node = map.get(context.getName());
          if (node == null) {
            // 创建一个 DefaultNode,并放入缓存 map
            node = new DefaultNode(resourceWrapper, null);
            HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
            cacheMap.putAll(map);
            cacheMap.put(context.getName(), node);
            map = cacheMap;
            // Build invocation tree
            // 将新建 node 添加到调用树中
            ((DefaultNode) context.getLastNode()).addChild(node);
          }
    
        }
      }
    
      context.setCurNode(node);
      // 触发下一个节点~
      fireEntry(context, resourceWrapper, node, count, prioritized, args);
     }
    
     @Override
     public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
      fireExit(context, resourceWrapper, count, args);
     }
    
    }
    

    b. ClusterBuilderSlot

    /**
     * This slot maintains resource running statistics (response time, qps, thread
     * count, exception), and a list of callers as well which is marked by
     * {@link ContextUtil#enter(String origin)}
     *
     * One resource has only one cluster node, while one resource can have multiple default nodes.
     */
    @Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT)
    public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
      /**
       *
       * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share the same
       * {@link ProcessorSlotChain} globally, no matter in which context. So if code goes into
       * {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)},
       * the resource name must be same but context name may not.
       *
       * To get total statistics of the same resource in different context, same resource
       * shares the same {@link ClusterNode} globally. All {@link ClusterNode}s are cached
       * in this map.
       *
       * The longer the application runs, the more stable this mapping will
       * become. so we don't concurrent map but a lock. as this lock only happens
       * at the very beginning while concurrent map will hold the lock all the time.
       *
       */
      private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();
    
      private static final Object lock = new Object();
    
      private volatile ClusterNode clusterNode = null;
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node,
              int count, boolean prioritized, Object... args) throws Throwable {
        if (clusterNode == null) {
          synchronized (lock) {
            if (clusterNode == null) {
              // Create the cluster node.
              clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
              HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
              newMap.putAll(clusterNodeMap);
              newMap.put(node.getId(), clusterNode);
    
              clusterNodeMap = newMap;
            }
          }
        }
        node.setClusterNode(clusterNode);
    
        /*
         * if context origin is set, we should get or create a new {@link Node}
         * of the specific origin.
         */
        if (!"".equals(context.getOrigin())) {
          Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
          context.getCurEntry().setOriginNode(originNode);
        }
    
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
      }
    
      @Override
      public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
      }
    
      /**
       * Get {@link ClusterNode} of the resource of the specific type.
       *
       * @param id   resource name.
       * @param type invoke type.
       * @return the {@link ClusterNode}
       */
      public static ClusterNode getClusterNode(String id, EntryType type) {
        return clusterNodeMap.get(new StringResourceWrapper(id, type));
      }
    
      /**
       * Get {@link ClusterNode} of the resource name.
       *
       * @param id resource name.
       * @return the {@link ClusterNode}.
       */
      public static ClusterNode getClusterNode(String id) {
        if (id == null) {
          return null;
        }
        ClusterNode clusterNode = null;
    
        for (EntryType nodeType : EntryType.values()) {
          clusterNode = clusterNodeMap.get(new StringResourceWrapper(id, nodeType));
          if (clusterNode != null) {
            break;
          }
        }
    
        return clusterNode;
      }
    
      /**
       * Get {@link ClusterNode}s map, this map holds all {@link ClusterNode}s,
       * it's key is resource name, value is the related {@link ClusterNode}.
       * DO NOT MODIFY the map returned.
       *
       * @return all {@link ClusterNode}s
       */
      public static Map<ResourceWrapper, ClusterNode> getClusterNodeMap() {
        return clusterNodeMap;
      }
    
      /**
       * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL}
       * or {@link SampleCountProperty#SAMPLE_COUNT} is changed.
       */
      public static void resetClusterNodes() {
        for (ClusterNode node : clusterNodeMap.values()) {
          node.reset();
        }
      }
    }
    

    c. StatisticSlot

    /**
     * A processor slot that dedicates to real time statistics.
     * 
     * When entering this slot, we need to separately count the following information:
     * - {@link ClusterNode}: total statistics of a cluster node of the resource ID.
     * - Origin node: statistics of a cluster node from different callers/origins.
     * - {@link DefaultNode}: statistics for specific resource name in the specific context.
     * - Finally, the sum statistics of all entrances.
     */
    @Spi(order = Constants.ORDER_STATISTIC_SLOT)
    public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node,
              int count, boolean prioritized, Object... args) throws Throwable {
        try {
          // Do some checking.
          // 调用 SlotChain 中后续的所有 Slot,完成所有规则检测
          // 其在执行过程中可能会抛出异常,例如,规则检测未通过,抛出 BlockException
          fireEntry(context, resourceWrapper, node, count, prioritized, args);
    
          // ============ Request passed, add thread count and pass count. ============
          // 代码能走到这里,说明前面所有规则检测全部通过,此时就可以将该请求统计到相应数据中了
    
          // ~> 增加线程数据
          node.increaseThreadNum();
          // ~> 增加通过的请求数量
          node.addPassRequest(count);
    
          if (context.getCurEntry().getOriginNode() != null) {
            // Add count for origin node.
            context.getCurEntry().getOriginNode().increaseThreadNum();
            context.getCurEntry().getOriginNode().addPassRequest(count);
          }
    
          if (resourceWrapper.getEntryType() == EntryType.IN) {
            // Add count for global inbound entry node for global statistics.
            Constants.ENTRY_NODE.increaseThreadNum();
            Constants.ENTRY_NODE.addPassRequest(count);
          }
    
          // Handle pass event with registered entry callback handlers.
          for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
            handler.onPass(context, resourceWrapper, node, count, args);
          }
        } catch (PriorityWaitException ex) {
          node.increaseThreadNum();
          if (context.getCurEntry().getOriginNode() != null) {
            // Add count for origin node.
            context.getCurEntry().getOriginNode().increaseThreadNum();
          }
    
          if (resourceWrapper.getEntryType() == EntryType.IN) {
            // Add count for global inbound entry node for global statistics.
            Constants.ENTRY_NODE.increaseThreadNum();
          }
          // Handle pass event with registered entry callback handlers.
          for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
            handler.onPass(context, resourceWrapper, node, count, args);
          }
        } catch (BlockException e) {
          // Blocked, set block exception to current entry.
          context.getCurEntry().setBlockError(e);
    
          // Add block count.
          node.increaseBlockQps(count);
          if (context.getCurEntry().getOriginNode() != null) {
            context.getCurEntry().getOriginNode().increaseBlockQps(count);
          }
    
          if (resourceWrapper.getEntryType() == EntryType.IN) {
            // Add count for global inbound entry node for global statistics.
            Constants.ENTRY_NODE.increaseBlockQps(count);
          }
    
          // Handle block event with registered entry callback handlers.
          for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
            handler.onBlocked(e, context, resourceWrapper, node, count, args);
          }
    
          throw e;
        } catch (Throwable e) {
          // Unexpected internal error, set error to current entry.
          context.getCurEntry().setError(e);
    
          throw e;
        }
      }
    
      @Override
      public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        Node node = context.getCurNode();
    
        if (context.getCurEntry().getBlockError() == null) {
          // Calculate response time (use completeStatTime as the time of completion).
          long completeStatTime = TimeUtil.currentTimeMillis();
          context.getCurEntry().setCompleteTimestamp(completeStatTime);
          long rt = completeStatTime - context.getCurEntry().getCreateTimestamp();
    
          Throwable error = context.getCurEntry().getError();
    
          // Record response time and success count.
          recordCompleteFor(node, count, rt, error);
          recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error);
          if (resourceWrapper.getEntryType() == EntryType.IN) {
            recordCompleteFor(Constants.ENTRY_NODE, count, rt, error);
          }
        }
    
        // Handle exit event with registered exit callback handlers.
        Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
        for (ProcessorSlotExitCallback handler : exitCallbacks) {
          handler.onExit(context, resourceWrapper, count, args);
        }
    
        fireExit(context, resourceWrapper, count);
      }
    
      private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) {
        if (node == null) {
          return;
        }
        node.addRtAndSuccess(rt, batchCount);
        node.decreaseThreadNum();
    
        if (error != null && !(error instanceof BlockException)) {
          node.increaseExceptionQps(batchCount);
        }
      }
    }
    

    d. FlowSlot

    /**
     *
     * Combined the runtime statistics collected from the previous
     * slots (NodeSelectorSlot, ClusterNodeBuilderSlot, and StatisticSlot), FlowSlot
     * will use pre-set rules to decide whether the incoming requests should be
     * blocked.
     *
     *
     *
     * {@code SphU.entry(resourceName)} will throw {@code FlowException} if any rule is
     * triggered. Users can customize their own logic by catching {@code FlowException}.
     *
     *
     *
     * One resource can have multiple flow rules. FlowSlot traverses these rules
     * until one of them is triggered or all rules have been traversed.
     *
     *
     *
     * Each {@link FlowRule} is mainly composed of these factors: grade, strategy, path. We
     * can combine these factors to achieve different effects.
     *
     *
     *
     * The grade is defined by the {@code grade} field in {@link FlowRule}. Here, 0 for thread
     * isolation and 1 for request count shaping (QPS). Both thread count and request
     * count are collected in real runtime, and we can view these statistics by
     * following command:
     *
     * curl http://localhost:8719/tree
     *
     * idx id  thread pass  blocked   success total aRt   1m-pass   1m-block   1m-all   exception
     * 2   abc647 0    460  46      46   1  27    630     276    897    0
     *
     *
     *
     * {@code thread} for the count of threads that is currently processing the resource
     * {@code pass} for the count of incoming request within one second
     * {@code blocked} for the count of requests blocked within one second
     * {@code success} for the count of the requests successfully handled by Sentinel within one second
     * {@code RT} for the average response time of the requests within a second
     * {@code total} for the sum of incoming requests and blocked requests within one second
     * {@code 1m-pass} is for the count of incoming requests within one minute
     * {@code 1m-block} is for the count of a request blocked within one minute
     * {@code 1m-all} is the total of incoming and blocked requests within one minute
     * {@code exception} is for the count of business (customized) exceptions in one second
     *
     *
     * This stage is usually used to protect resources from occupying. If a resource
     * takes long time to finish, threads will begin to occupy. The longer the
     * response takes, the more threads occupy.
     *
     * Besides counter, thread pool or semaphore can also be used to achieve this.
     *
     * - Thread pool: Allocate a thread pool to handle these resource. When there is
     *     no more idle thread in the pool, the request is rejected without affecting
     *     other resources.
     *
     * - Semaphore: Use semaphore to control the concurrent count of the threads in
     *     this resource.
     *
     * The benefit of using thread pool is that, it can walk away gracefully when
     * time out. But it also bring us the cost of context switch and additional
     * threads. If the incoming requests is already served in a separated thread,
     * for instance, a Servlet HTTP request, it will almost double the threads count if
     * using thread pool.
     *
     * Traffic Shaping
     *
     * When QPS exceeds the threshold, Sentinel will take actions to control the incoming request,
     * and is configured by {@code controlBehavior} field in flow rules.
     *
     *
     * Immediately reject ({@code RuleConstant.CONTROL_BEHAVIOR_DEFAULT})
     *
     * This is the default behavior. The exceeded request is rejected immediately
     * and the FlowException is thrown
     *
     *
     * Warmup ({@code RuleConstant.CONTROL_BEHAVIOR_WARM_UP})
     *
     * If the load of system has been low for a while, and a large amount of
     * requests comes, the system might not be able to handle all these requests at
     * once. However if we steady increase the incoming request, the system can warm
     * up and finally be able to handle all the requests.
     * This warmup period can be configured by setting the field {@code warmUpPeriodSec}
     * in flow rules.
     *
     *
     * Uniform Rate Limiting ({@code RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER})
     *
     * This strategy strictly controls the interval between requests.
     * In other words, it allows requests to pass at a stable, uniform rate.
     *
     * https://raw.githubusercontent.com/wiki/alibaba/Sentinel/image/uniform-speed-queue.png
     *
     * This strategy is an implement of <a href="https://en.wikipedia.org/wiki/Leaky_bucket">
     * leaky bucket.
     * It is used to handle the request at a stable rate and is often used in burst traffic
     * (e.g. message handling).
     *
     * When a large number of requests beyond the system’s capacity arrive
     * at the same time, the system using this strategy will handle requests and its
     * fixed rate until all the requests have been processed or time out.
     */
    @Spi(order = Constants.ORDER_FLOW_SLOT)
    public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
      private final FlowRuleChecker checker;
    
      public FlowSlot() {
        this(new FlowRuleChecker());
      }
    
      /**
       * Package-private for test.
       *
       * @param checker flow rule checker
       * @since 1.6.1
       */
      FlowSlot(FlowRuleChecker checker) {
        AssertUtil.notNull(checker, "flow checker should not be null");
        this.checker = checker;
      }
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node,
                int count, boolean prioritized, Object... args) throws Throwable {
        // =====> 检测并应用流控规则
        checkFlow(resourceWrapper, context, node, count, prioritized);
        // 触发下一个 Slot
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
      }
    
      void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
      }
    
      @Override
      public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
      }
    
      private final Function<String, Collection<FlowRule>> ruleProvider = new Function<String, Collection<FlowRule>>() {
        @Override
        public Collection<FlowRule> apply(String resource) {
          // Flow rule map should not be null.
          // 获取到所有资源的流控规则 map 的 key 为资源名称,value 为该资源上加载的所有流控规则
          Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
          // 获取指定资源的所有流控规则
          return flowRules.get(resource);
        }
      };
    }
    

    e. DegradeSlot

    /**
     * A {@link ProcessorSlot} dedicates to circuit breaking.
     */
    @Spi(order = Constants.ORDER_DEGRADE_SLOT)
    public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    
      @Override
      public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node,
              int count, boolean prioritized, Object... args) throws Throwable {
        // 完成熔断降级检测
        performChecking(context, resourceWrapper);
        // 触发下一个节点
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
      }
    
      void performChecking(Context context, ResourceWrapper r) throws BlockException {
        // 获取到当前资源的所有熔断器
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
        // 若熔断器为空,则直结束
        if (circuitBreakers == null || circuitBreakers.isEmpty()) {
          return;
        }
    
        for (CircuitBreaker cb : circuitBreakers) {
          // =====> 逐个尝试所有熔断器。若没有通过当前熔断器,则直接抛出异常
          if (!cb.tryPass(context)) {
            throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
          }
        }
      }
    
      @Override
      public void exit(Context context, ResourceWrapper r, int count, Object... args) {
        Entry curEntry = context.getCurEntry();
        if (curEntry.getBlockError() != null) {
          fireExit(context, r, count, args);
          return;
        }
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
        if (circuitBreakers == null || circuitBreakers.isEmpty()) {
          fireExit(context, r, count, args);
          return;
        }
    
        if (curEntry.getBlockError() == null) {
          // passed request
          for (CircuitBreaker circuitBreaker : circuitBreakers) {
            circuitBreaker.onRequestComplete(context);
          }
        }
    
        fireExit(context, r, count, args);
      }
    }
    

    4.2 FlowSlot_Ext

    a. Rule~Bean

    AbstractRule

    public abstract class AbstractRule implements Rule {
    
        /**
         * 资源名称
         *
         * Resource name.
         */
        private String resource;
    
        /**
         * 请求来源
         *
         * Application name that will be limited by origin.
         * The default limitApp is {@code default}, which means allowing all origin apps.
         *
         * For authority rules, multiple origin name can be separated with comma (',').
         */
        private String limitApp;
    
        // ...
    }
    

    FlowRule

    public class FlowRule extends AbstractRule {
    
      public FlowRule() {
        super();
        setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
      }
    
      public FlowRule(String resourceName) {
        super();
        setResource(resourceName);
        setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
      }
    
      /**
       * 阈值类型:0 表示线程数限流、1 表示 QPS 限流
       * The threshold type of flow control (0: thread count, 1: QPS).
       */
      private int grade = RuleConstant.FLOW_GRADE_QPS;
    
      /**
       * [阈值] Flow control threshold count.
       */
      private double count;
    
      /**
       * [流控模式] Flow control strategy based on invocation chain.
       *
       * {RuleConstant#STRATEGY_DIRECT#直接流控} for direct flow control (by origin);
       * {RuleConstant#STRATEGY_RELATE#关联流控} for relevant flow control (with relevant resource);
       * {RuleConstant#STRATEGY_CHAIN #链路流控} for chain flow control (by entrance resource).
       */
      private int strategy = RuleConstant.STRATEGY_DIRECT;
    
      /**
       * 若流控模式为关联流控时的关联资源
       * Reference resource in flow control with relevant resource or context.
       */
      private String refResource;
    
      /**
       * [流控效果] Rate limiter control behavior.
       * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
       * 0-快速失败,1-预热令牌桶算法),2-排队等待(漏斗算法),3-预热+排队等待
       */
      private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
    
      /**
       * warm up 预热时长
       */
      private int warmUpPeriodSec = 10;
    
      /**
       * 排队等待的超时时间 Max queueing time in rate limiter behavior.
       */
      private int maxQueueingTimeMs = 500;
    
      /**
       * 是否集群模式
       */
      private boolean clusterMode;
      /**
       * Flow rule config for cluster mode.
       */
      private ClusterFlowConfig clusterConfig;
    
      /**
       * The traffic shaping (throttling) controller.
       */
      private TrafficShapingController controller;
    
      // ...
    }
    

    b. FlowRuleChecker

    /**
     * Rule checker for flow control rules.
     */
    public class FlowRuleChecker {
    
      public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) {
          return;
        }
        // 获取到指定资源的所有流控规则
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
          for (FlowRule rule : rules) {
            // =====> 逐个应用流控规则。若无法通过则抛出异常,后续规则不再应用
            if (!canPassCheck(rule, context, node, count, prioritized)) {
              throw new FlowException(rule.getLimitApp(), rule);
            }
          }
        }
      }
    
      public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount) {
        return canPassCheck(rule, context, node, acquireCount, false);
      }
    
      public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, boolean prioritized) {
    
        // 从规则中获取要限定的来源
        String limitApp = rule.getLimitApp();
    
        // 若限流的来源为 null,则请求直接通过
        if (limitApp == null) {
          return true;
        }
    
        // 使用规则处理〈集群流控〉
        if (rule.isClusterMode()) {
          return passClusterCheck(rule, context, node, acquireCount, prioritized);
        }
    
        // =====> 使用规则处理〈单机流控〉
        return passLocalCheck(rule, context, node, acquireCount, prioritized);
      }
    
      private static boolean passLocalCheck(FlowRule rule, Context context,
    			DefaultNode node, int acquireCount, boolean prioritized) {
        // 通过 rule 形成 selectedRuleNode
        Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
        // 若没有选择出 ruleNode,说明没有规则,则直接返回 true,表示通过检测
        if (selectedNode == null) {
          return true;
        }
    
        // =====> 使用规则进行逐项检测(见下图)
        return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
      }
    
      static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
        String refResource = rule.getRefResource();
        int strategy = rule.getStrategy();
    
        if (StringUtil.isEmpty(refResource)) {
          return null;
        }
    
        if (strategy == RuleConstant.STRATEGY_RELATE) {
          return ClusterBuilderSlot.getClusterNode(refResource);
        }
    
        if (strategy == RuleConstant.STRATEGY_CHAIN) {
          if (!refResource.equals(context.getName())) {
            return null;
          }
          return node;
        }
        // No node.
        return null;
      }
    
      private static boolean filterOrigin(String origin) {
        // Origin cannot be `default` or `other`.
        return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin);
      }
    
      static Node selectNodeByRequesterAndStrategy(FlowRule rule, Context context, DefaultNode node) {
        // The limit app should not be empty.
        String limitApp = rule.getLimitApp();
        int strategy = rule.getStrategy();
        String origin = context.getOrigin();
    
        if (limitApp.equals(origin) && filterOrigin(origin)) {
          if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // Matches limit origin, return origin statistic node.
            return context.getOriginNode();
          }
    
          return selectReferenceNode(rule, context, node);
        } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
          if (strategy == RuleConstant.STRATEGY_DIRECT) {
            // Return the cluster node.
            return node.getClusterNode();
          }
    
          return selectReferenceNode(rule, context, node);
        } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
    			&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
          if (strategy == RuleConstant.STRATEGY_DIRECT) {
            return context.getOriginNode();
          }
    
          return selectReferenceNode(rule, context, node);
        }
    
        return null;
      }
    
      private static boolean passClusterCheck(FlowRule rule, Context context,
    			DefaultNode node, int acquireCount, boolean prioritized) {
        try {
          TokenService clusterService = pickClusterService();
          if (clusterService == null) {
            return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
          }
          long flowId = rule.getClusterConfig().getFlowId();
          TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);
          return applyTokenResult(result, rule, context, node, acquireCount, prioritized);
          // If client is absent, then fallback to local mode.
        } catch (Throwable ex) {
          RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);
        }
        // Fallback to local flow control when token client or server for this rule is not available.
        // If fallback is not enabled, then directly pass.
        return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
      }
    
      private static boolean fallbackToLocalOrPass(FlowRule rule, Context context,
    			DefaultNode node, int acquireCount, boolean prioritized) {
        if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {
          return passLocalCheck(rule, context, node, acquireCount, prioritized);
        } else {
          // The rule won't be activated, just pass.
          return true;
        }
      }
    
      private static TokenService pickClusterService() {
        if (ClusterStateManager.isClient()) {
          return TokenClientProvider.getClient();
        }
        if (ClusterStateManager.isServer()) {
          return EmbeddedClusterTokenServerProvider.getServer();
        }
        return null;
      }
    
      private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule,
    			Context context, DefaultNode node, int acquireCount, boolean prioritized) {
        switch (result.getStatus()) {
          case TokenResultStatus.OK:
            return true;
          case TokenResultStatus.SHOULD_WAIT:
            // Wait for next tick.
            try {
              Thread.sleep(result.getWaitInMs());
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            return true;
          case TokenResultStatus.NO_RULE_EXISTS:
          case TokenResultStatus.BAD_REQUEST:
          case TokenResultStatus.FAIL:
          case TokenResultStatus.TOO_MANY_REQUEST:
            return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
          case TokenResultStatus.BLOCKED:
          default:
            return false;
        }
      }
    }
    

    使用规则进行逐项检测:

    c. TrafficShapingController

    /**
     * A universal interface for traffic shaping controller.
     */
    public interface TrafficShapingController {
    
      /**
       * Check whether given resource entry can pass with provided count.
       *
       * @param node resource node
       * @param acquireCount count to acquire
       * @param prioritized whether the request is prioritized
       * @return true if the resource entry can pass; false if it should be blocked
       */
      boolean canPass(Node node, int acquireCount, boolean prioritized);
    
      /**
       * Check whether given resource entry can pass with provided count.
       *
       * @param node resource node
       * @param acquireCount count to acquire
       * @return true if the resource entry can pass; false if it should be blocked
       */
      boolean canPass(Node node, int acquireCount);
    }
    

    DefaultController

    public class DefaultController implements TrafficShapingController {
    
      // ...
    
      @Override
      public boolean canPass(Node node, int acquireCount) {
        return canPass(node, acquireCount, false);
      }
    
      /**
       * 快速失败的流控效果中的通过性判断
       * @param node resource node
       * @param acquireCount count to acquire
       * @param prioritized whether the request is prioritized
       * @return
       */
      @Override
      public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        // 获取当前时间窗中已经统计的数据
        int curCount = avgUsedTokens(node);
    
        // 已经统计的数据 + 本次请求的数量
        // - 若 > 设置的阈值,则返回 false 表示没有通过检测
        // - 若 <= 阈值,则返回true 表示通过检测
        if (curCount + acquireCount > count) {
          if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
            long currentTime;
            long waitInMs;
            currentTime = TimeUtil.currentTimeMillis();
            waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
            if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
              node.addWaitingRequest(currentTime + waitInMs, acquireCount);
              node.addOccupiedPass(acquireCount);
              sleep(waitInMs);
    
              // PriorityWaitException indicates that the request will pass
              // after waiting for {@link @waitInMs}.
    
              throw new PriorityWaitException(waitInMs);
            }
          }
          return false;
        }
        return true;
      }
    
      private int avgUsedTokens(Node node) {
        // 若没有选择出 node,则说明没有做统计工作,直接返回 0
        if (node == null) {
          return DEFAULT_AVG_USED_TOKENS;
        }
    
        // 若阈值类型为线程数,则直接返回当前的线程数量;
        // 若阈值类型为 QPS,则返回统计的当前的 QPS
        return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
      }
    }
    

    RateLimiterController

    WarmUpController

    WarmUpRateLimiterController

    4.3 DegradeSlot_Ext

    a. CircuitBreaker

    /**
     * Sentinel 1.8 将 3 种熔断策略(慢调用/异常比/异常数)封装为为 2 种熔断器:
     *    1. 响应时间熔断器 ResponseTimeCircuitBreaker
     *    2. 异常熔断器 ExceptionCircuitBreaker
     */
    public interface CircuitBreaker {
    
      /**
       * 获取降级规则 Get the associated circuit breaking rule.
       * @return associated circuit breaking rule
       */
      DegradeRule getRule();
    
      /**
       * 判断请求是否可以通过,返回 true,表示通过,则不用降级;否则降级
       *
       * Acquires permission of an invocation only if it is available at the time of invoking.
       *
       * @param context context of current invocation
       * @return {@code true} if permission was acquired and {@code false} otherwise
       */
      boolean tryPass(Context context);
    
      /**
       * 获取当前熔断器状态
       *
       * Get current state of the circuit breaker.
       * @return current state of the circuit breaker
       */
      State currentState();
    
      /**
       * 回调方法:当请求通过并完成后会触发
       *
       * Record a completed request with the context and handle
       * state transformation of the circuit breaker.
       * Called when a passed invocation finished.
       * @param context context of current invocation
       */
      void onRequestComplete(Context context);
    
      /**
       * Circuit breaker state.
       */
      enum State {
        /**
         * 打开状态,会拒绝所有请求
         *
         * In {@code OPEN} state, all requests will be rejected until the next recovery time point.
         */
        OPEN,
        /**
         * 过滤状态
         *
         * In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation.
         * If the invocation is abnormal according to the strategy (e.g. it's slow),
         * the circuit breaker will re-transform to the {@code OPEN} state and
         * wait for the next recovery time point;
         *
         * otherwise the resource will be regarded as "recovered" and the circuit breaker
         * will cease cutting off requests and transform to {@code CLOSED} state.
         */
        HALF_OPEN,
        /**
         * 关闭状态,所有请求可以通过
         *
         * In {@code CLOSED} state, all requests are permitted.
         * When current metric value exceeds the threshold,
         * the circuit breaker will transform to {@code OPEN} state.
         */
        CLOSED
      }
    }
    

    b. AbstractCircuitBreaker

    public abstract class AbstractCircuitBreaker implements CircuitBreaker {
    
      protected final DegradeRule rule;
      protected final int recoveryTimeoutMs;
    
      private final EventObserverRegistry observerRegistry;
    
      protected final AtomicReference<State> currentState = new AtomicReference<>(State.CLOSED);
      protected volatile long nextRetryTimestamp;
    
      public AbstractCircuitBreaker(DegradeRule rule) {
        this(rule, EventObserverRegistry.getInstance());
      }
    
      AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) {
        AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null");
        if (!DegradeRuleManager.isValidRule(rule)) {
          throw new IllegalArgumentException("Invalid DegradeRule: " + rule);
        }
        this.observerRegistry = observerRegistry;
        this.rule = rule;
        this.recoveryTimeoutMs = rule.getTimeWindow() * 1000;
      }
    
      @Override
      public DegradeRule getRule() {
        return rule;
      }
    
      @Override
      public State currentState() {
        return currentState.get();
      }
    
      // =====================================================================
    
      @Override
      public boolean tryPass(Context context) {
        // 熔断器状态为关闭状态,则请求可以通过
        if (currentState.get() == State.CLOSED) {
          return true;
        }
    
        // 熔断器状态为打开状态,此时再查看,
        // 若下次时间窗时间点已经到达,且熔断器成功由 Open 变为了 Half-Open,则请求通过
        if (currentState.get() == State.OPEN) {
          // For half-open state we allow a request for probing.
          return retryTimeoutArrived() && fromOpenToHalfOpen(context);
        }
        return false;
      }
    
      // =====================================================================
    
      /**
       * Reset the statistic data.
       */
      abstract void resetStat();
    
      protected boolean retryTimeoutArrived() {
        return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
      }
    
      protected void updateNextRetryTimestamp() {
        this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs;
      }
    
      protected boolean fromCloseToOpen(double snapshotValue) {
        State prev = State.CLOSED;
        if (currentState.compareAndSet(prev, State.OPEN)) {
          updateNextRetryTimestamp();
    
          notifyObservers(prev, State.OPEN, snapshotValue);
          return true;
        }
        return false;
      }
    
      protected boolean fromOpenToHalfOpen(Context context) {
        if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
          notifyObservers(State.OPEN, State.HALF_OPEN, null);
          Entry entry = context.getCurEntry();
          entry.whenTerminate(new BiConsumer<Context, Entry>() {
            @Override
            public void accept(Context context, Entry entry) {
              // Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638
              // Without the hook, the circuit breaker won't recover from half-open state in some circumstances
              // when the request is actually blocked by upcoming rules (not only degrade rules).
              if (entry.getBlockError() != null) {
                // Fallback to OPEN due to detecting request is blocked
                currentState.compareAndSet(State.HALF_OPEN, State.OPEN);
                notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);
              }
            }
          });
          return true;
        }
        return false;
      }
    
      private void notifyObservers(CircuitBreaker.State prevState, CircuitBreaker.State newState, Double snapshotValue) {
        for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
          observer.onStateChange(prevState, newState, rule, snapshotValue);
        }
      }
    
      protected boolean fromHalfOpenToOpen(double snapshotValue) {
        if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) {
          updateNextRetryTimestamp();
          notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue);
          return true;
        }
        return false;
      }
    
      protected boolean fromHalfOpenToClose() {
        if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) {
          resetStat();
          notifyObservers(State.HALF_OPEN, State.CLOSED, null);
          return true;
        }
        return false;
      }
    
      protected void transformToOpen(double triggerValue) {
        State cs = currentState.get();
        switch (cs) {
          case CLOSED:
            fromCloseToOpen(triggerValue);
            break;
          case HALF_OPEN:
            fromHalfOpenToOpen(triggerValue);
            break;
          default:
            break;
        }
      }
    }
    

    c. ExceptionCircuitBreaker*

    public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {
    
      private final int strategy;
      private final int minRequestAmount;
      private final double threshold;
    
      private final LeapArray<SimpleErrorCounter> stat;
    
      public ExceptionCircuitBreaker(DegradeRule rule) {
        this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
      }
    
      ExceptionCircuitBreaker(DegradeRule rule, LeapArray<SimpleErrorCounter> stat) {
        super(rule);
        this.strategy = rule.getGrade();
        boolean modeOk =
                    strategy == DEGRADE_GRADE_EXCEPTION_RATIO
                        || strategy == DEGRADE_GRADE_EXCEPTION_COUNT;
        AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count");
        AssertUtil.notNull(stat, "stat cannot be null");
        this.minRequestAmount = rule.getMinRequestAmount();
        this.threshold = rule.getCount();
        this.stat = stat;
      }
    
      @Override
      protected void resetStat() {
        // Reset current bucket (bucket count = 1).
        stat.currentWindow().value().reset();
      }
    
      @Override
      public void onRequestComplete(Context context) {
        Entry entry = context.getCurEntry();
        if (entry == null) {
          return;
        }
        Throwable error = entry.getError();
        SimpleErrorCounter counter = stat.currentWindow().value();
        if (error != null) {
          counter.getErrorCount().add(1);
        }
        counter.getTotalCount().add(1);
    
        handleStateChangeWhenThresholdExceeded(error);
      }
    
      private void handleStateChangeWhenThresholdExceeded(Throwable error) {
        if (currentState.get() == State.OPEN) {
          return;
        }
    
        if (currentState.get() == State.HALF_OPEN) {
          // In detecting request
          if (error == null) {
            fromHalfOpenToClose();
          } else {
            fromHalfOpenToOpen(1.0d);
          }
          return;
        }
    
        List<SimpleErrorCounter> counters = stat.values();
        long errCount = 0;
        long totalCount = 0;
        for (SimpleErrorCounter counter : counters) {
          errCount += counter.errorCount.sum();
          totalCount += counter.totalCount.sum();
        }
        if (totalCount < minRequestAmount) {
          return;
        }
        double curCount = errCount;
        if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
          // Use errorRatio
          curCount = errCount * 1.0d / totalCount;
        }
        if (curCount > threshold) {
          transformToOpen(curCount);
        }
      }
    
      static class SimpleErrorCounter {
        private LongAdder errorCount;
        private LongAdder totalCount;
    
        public SimpleErrorCounter() {
          this.errorCount = new LongAdder();
          this.totalCount = new LongAdder();
        }
    
        public LongAdder getErrorCount() {
          return errorCount;
        }
    
        public LongAdder getTotalCount() {
          return totalCount;
        }
    
        public SimpleErrorCounter reset() {
          errorCount.reset();
          totalCount.reset();
          return this;
        }
    
        @Override
        public String toString() {
          return "SimpleErrorCounter{" +
            "errorCount=" + errorCount +
            ", totalCount=" + totalCount +
            '}';
        }
      }
    
      static class SimpleErrorCounterLeapArray extends LeapArray<SimpleErrorCounter> {
    
        public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) {
          super(sampleCount, intervalInMs);
        }
    
        @Override
        public SimpleErrorCounter newEmptyBucket(long timeMillis) {
          return new SimpleErrorCounter();
        }
    
        @Override
        protected WindowWrap<SimpleErrorCounter> resetWindowTo(WindowWrap<SimpleErrorCounter> w, long startTime) {
          // Update the start time and reset value.
          w.resetTo(startTime);
          w.value().reset();
          return w;
        }
      }
    }
    

    d. ResponseTimeCircuitBreaker*

    public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {
    
      private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d;
    
      private final long maxAllowedRt;
      private final double maxSlowRequestRatio;
      private final int minRequestAmount;
    
      private final LeapArray<SlowRequestCounter> slidingCounter;
    
      public ResponseTimeCircuitBreaker(DegradeRule rule) {
        this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
      }
    
      ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray<SlowRequestCounter> stat) {
        super(rule);
        AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT");
        AssertUtil.notNull(stat, "stat cannot be null");
        this.maxAllowedRt = Math.round(rule.getCount());
        this.maxSlowRequestRatio = rule.getSlowRatioThreshold();
        this.minRequestAmount = rule.getMinRequestAmount();
        this.slidingCounter = stat;
      }
    
      @Override
      public void resetStat() {
        // Reset current bucket (bucket count = 1).
        slidingCounter.currentWindow().value().reset();
      }
    
      @Override
      public void onRequestComplete(Context context) {
        SlowRequestCounter counter = slidingCounter.currentWindow().value();
        Entry entry = context.getCurEntry();
        if (entry == null) {
          return;
        }
        long completeTime = entry.getCompleteTimestamp();
        if (completeTime <= 0) {
          completeTime = TimeUtil.currentTimeMillis();
        }
        long rt = completeTime - entry.getCreateTimestamp();
        if (rt > maxAllowedRt) {
          counter.slowCount.add(1);
        }
        counter.totalCount.add(1);
    
        handleStateChangeWhenThresholdExceeded(rt);
      }
    
      private void handleStateChangeWhenThresholdExceeded(long rt) {
        if (currentState.get() == State.OPEN) {
          return;
        }
        
        if (currentState.get() == State.HALF_OPEN) {
          // In detecting request
          // TODO: improve logic for half-open recovery
          if (rt > maxAllowedRt) {
            fromHalfOpenToOpen(1.0d);
          } else {
            fromHalfOpenToClose();
          }
          return;
        }
    
        List<SlowRequestCounter> counters = slidingCounter.values();
        long slowCount = 0;
        long totalCount = 0;
        for (SlowRequestCounter counter : counters) {
          slowCount += counter.slowCount.sum();
          totalCount += counter.totalCount.sum();
        }
        if (totalCount < minRequestAmount) {
          return;
        }
        double currentRatio = slowCount * 1.0d / totalCount;
        if (currentRatio > maxSlowRequestRatio) {
          transformToOpen(currentRatio);
        }
        if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&
            Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {
          transformToOpen(currentRatio);
        }
      }
    
      static class SlowRequestCounter {
        private LongAdder slowCount;
        private LongAdder totalCount;
    
        public SlowRequestCounter() {
          this.slowCount = new LongAdder();
          this.totalCount = new LongAdder();
        }
    
        public LongAdder getSlowCount() {
          return slowCount;
        }
    
        public LongAdder getTotalCount() {
          return totalCount;
        }
    
        public SlowRequestCounter reset() {
          slowCount.reset();
          totalCount.reset();
          return this;
        }
    
        @Override
        public String toString() {
          return "SlowRequestCounter{" +
            "slowCount=" + slowCount +
            ", totalCount=" + totalCount +
            '}';
        }
      }
    
      static class SlowRequestLeapArray extends LeapArray<SlowRequestCounter> {
    
        public SlowRequestLeapArray(int sampleCount, int intervalInMs) {
          super(sampleCount, intervalInMs);
        }
    
        @Override
        public SlowRequestCounter newEmptyBucket(long timeMillis) {
          return new SlowRequestCounter();
        }
    
        @Override
        protected WindowWrap<SlowRequestCounter> resetWindowTo(WindowWrap<SlowRequestCounter> w, long startTime) {
          w.resetTo(startTime);
          w.value().reset();
          return w;
        }
      }
    }
    

    5. 流程图

  • 相关阅读:
    FTP使用
    调用EJB的一点体会
    配置WEBLOGIC81连接缓冲池,提示JDBC类不在类路径的解决办法
    加载cab文件,有时候加载不上去,解决办法
    在SPS中无缝集成重设文档库下拉菜单
    statusful sessionbean 设置,不出现passivate 的问题,解决心得
    在IE耗时操作中加入进度条或进度框
    STS文档库的事件跟踪不能生效的解决办法
    学习新技术的一点体会
    在weblogic81中使用连接缓冲池成功
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/16126583.html
Copyright © 2020-2023  润新知