• Sentinel整体架构


    总体架构

    上图是来自官网的总体架构图,这张图上可以清晰的看到整个流量控制以责任链的模式进行的,每一个slot负责特定的处理,后续会给大家具体讲解chain上每一个slot的功能。

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

    模型设计

    上图只是展示了树状结构,无法清晰解释多线程下的执行态。

     

    在这图中可以清晰的看到资源的调用情况及统计的维度,包括早DefaultNode上的统计  ClusterNode上的统计,以及StatisticNode上的统计。

    在执行SphU.entry之前,首先会去创建EntranceNode入口   ContextUtil.enter(contextName, origin);

        protected static Context trueEnter(String name, String origin) {
            Context context = contextHolder.get();
            if (context == null) {
                // 一个context name 对应一个线程,  一个contextname又对应一个 EntranceNode
                Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
                DefaultNode node = localCacheNameMap.get(name);
                // DCL
                if (node == null) {
                    //  超过2000个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 {
                                    // 每一个资源名称都会对应
                                    node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                                    // Add entrance node.
                                    /**
                                     * root 本身就是EntranceNode  资源名为machine-root"
                                     * addChild,相当于放在上层node的list里面
                                     */
                                    Constants.ROOT.addChild(node);
    
                                    // copy-on-write
                                    Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                                    newMap.putAll(contextNameNodeMap);
                                    // 缓存顶层资源对应的EntranceNode
                                    newMap.put(name, node);
                                    contextNameNodeMap = newMap;
                                }
                            }
                        } finally {
                            LOCK.unlock();
                        }
                    }
                }
                // 保存了context name  以及对应的EntranceNode
                context = new Context(node, name);
                context.setOrigin(origin);
                // 设置到ThreadLocal中
                contextHolder.set(context);
            }
    
            return context;
        }

    实际上建立了线程与Context关系一对一关系 ,context name与EntranceNode一对一关系。接下来开始执行SphU.entry -> CtSph#entryWithPriority,  在此之前会做一些初始化,这块后续再讲解。

        private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
            throws BlockException {
            // 获取当前线程中context  ThreadLocal<Context>中获取
            Context context = ContextUtil.getContext();
            // 如果是 NullContext,那么说明 context name 超过了 2000 个,参见 ContextUtil#trueEnter
            // 这个时候,Sentinel 不再接受处理新的 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);
            }
    
            if (context == null) {
                // Using default context.
                context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
            }
    
            // Global switch is close, no rule checking will do.
            if (!Constants.ON) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
    
            /*
             * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
             * so no rule checking will be done.
             */
            if (chain == null) {
                return new CtEntry(resourceWrapper, null, context);
            }
    
            Entry e = new CtEntry(resourceWrapper, chain, context);
            try {
                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;
        }

    这边根据资源名称去获取对应的ProcessSlotChain调用链。也就是说相同资源名称的DefaultNode共享同一个chain。

    责任链构建

    调用链是是一个单向链表,上一个slot通过Next变量来持有下一个slot的引用,下图展示了责任链模式的数据结构。

     

    接下来看一下如何初始化调用链的,CtSph#lookProcessChain

    这边有一个概念 就是一个class类型 对应一个SpiLoader对象并缓存在内存中

        ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
            ProcessorSlotChain chain = chainMap.get(resourceWrapper);
            // DCL
            if (chain == null) {
                synchronized (LOCK) {
                    chain = chainMap.get(resourceWrapper);
                    if (chain == null) {
                        // Entry size limit.
                        if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                            return null;
                        }
    
                        // 创建资源调用链
                        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;
        }

    接下来会涉及到SPI相关的内容,对SPI不太熟悉的小伙伴,可以自行查阅资料。从SPI加载slot构建器

    slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();

    首先获取对应class类型的SpiLoader,根据classname保存在内存中

        public static <T> SpiLoader<T> of(Class<T> service) {
            AssertUtil.notNull(service, "SPI class cannot be null");
            AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()),
                    "SPI class[" + service.getName() + "] must be interface or abstract class");
    
            String className = service.getName();
            SpiLoader<T> spiLoader = SPI_LOADER_MAP.get(className);
            if (spiLoader == null) {
                synchronized (SpiLoader.class) {
                    spiLoader = SPI_LOADER_MAP.get(className);
                    if (spiLoader == null) {
                        SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service));
                        spiLoader = SPI_LOADER_MAP.get(className);
                    }
                }
            }
    
            return spiLoader;
        }

    接下来看一下loadFirstInstanceOrDefault方法

     public S loadFirstInstanceOrDefault() {
            // 加载class
            load();
    
            // 相同接口的实现类中  加载第一个
            for (Class<? extends S> clazz : classList) {
                if (defaultClass == null || clazz != defaultClass) {
                    return createInstance(clazz);
                }
            }
    
            // 如果没有 加载默认实现类
            return loadDefaultInstance();
        }
    /**
         * Load the Provider class from Provider configuration file
         */
        public void load() {
            // 如果加载过了 返回
            if (!loaded.compareAndSet(false, true)) {
                return;
            }
    
            // 获取文件路径
            String fullFileName = SPI_FILE_PREFIX + service.getName();
            ClassLoader classLoader;
            if (SentinelConfig.shouldUseContextClassloader()) {
                // 线程上下文加载器
                classLoader = Thread.currentThread().getContextClassLoader();
            } else {
                // 使用加载service的加载器   可能是系统类子加载器
                classLoader = service.getClassLoader();
            }
            if (classLoader == null) {
                //  使用系统类加载器加载
                classLoader = ClassLoader.getSystemClassLoader();
            }
            Enumeration<URL> urls = null;
            try {
                urls = classLoader.getResources(fullFileName);
            } catch (IOException e) {
                fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e);
            }
    
            if (urls == null || !urls.hasMoreElements()) {
                RecordLog.warn("No SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader);
                return;
            }
    
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
    
                InputStream in = null;
                BufferedReader br = null;
                try {
                    in = url.openStream();
                    br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
                    String line;
                    while ((line = br.readLine()) != null) {
                        // 每次读取一行
                        if (StringUtil.isBlank(line)) {
                            // Skip blank line
                            continue;
                        }
    
                        line = line.trim();
                        int commentIndex = line.indexOf("#");
                        if (commentIndex == 0) {
                            // 跳过注释
                            // Skip comment line
                            continue;
                        }
    
                        // 如果行开头不是#  取行头到#之间的字符串
                        if (commentIndex > 0) {
                            line = line.substring(0, commentIndex);
                        }
                        line = line.trim();
    
                        Class<S> clazz = null;
                        try {
                            // 加载到JVM
                            clazz = (Class<S>) Class.forName(line, false, classLoader);
                        } catch (ClassNotFoundException e) {
                            fail("class " + line + " not found", e);
                        }
    
                        // clazz 是否是service 的子类 或相同
                        if (!service.isAssignableFrom(clazz)) {
                            fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file=" + fullFileName);
                        }
    
                        // 缓存实现类
                        classList.add(clazz);
                        Spi spi = clazz.getAnnotation(Spi.class);
                        String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value();
                        if (classMap.containsKey(aliasName)) {
                            Class<? extends S> existClass = classMap.get(aliasName);
                            fail("Found repeat alias name for " + clazz.getName() + " and "
                                    + existClass.getName() + ",SPI configuration file=" + fullFileName);
                        }
                        // 别名与class  缓存
                        classMap.put(aliasName, clazz);
    
                        if (spi != null && spi.isDefault()) {
                            if (defaultClass != null) {
                                fail("Found more than one default Provider, SPI configuration file=" + fullFileName);
                            }
                            defaultClass = clazz;
                        }
    
                        RecordLog.info("[SpiLoader] Found SPI implementation for SPI {}, provider={}, aliasName={}"
                                + ", isSingleton={}, isDefault={}, order={}",
                            service.getName(), line, aliasName
                                , spi == null ? true : spi.isSingleton()
                                , spi == null ? false : spi.isDefault()
                                , spi == null ? 0 : spi.order());
                    }
                } catch (IOException e) {
                    fail("error reading SPI configuration file", e);
                } finally {
                    closeResources(in, br);
                }
            }
    
            // 根据order进行排序
            sortedClassList.addAll(classList);
            Collections.sort(sortedClassList, new Comparator<Class<? extends S>>() {
                @Override
                public int compare(Class<? extends S> o1, Class<? extends S> o2) {
                    Spi spi1 = o1.getAnnotation(Spi.class);
                    int order1 = spi1 == null ? 0 : spi1.order();
    
                    Spi spi2 = o2.getAnnotation(Spi.class);
                    int order2 = spi2 == null ? 0 : spi2.order();
    
                    return Integer.compare(order1, order2);
                }
            });
        }

    至此完成了chain构建器的类加载。后面通过构建器build一个调用链,同样使用SPI技术,slotChainBuilder.build();

    @Override
        public ProcessorSlotChain build() {
            ProcessorSlotChain chain = new DefaultProcessorSlotChain();
    
            // 获取到所有的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 into ProcessorSlotChain");
                    continue;
                }
    
                // ProcessorSlotChain中的end一直引用最新的slot
                //每一次的addlast 都会将当前最后一个slot的next引用到新的slot上
                //同时更新ProcessorSlotChain中的end为新的slot
                chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
            }
    
            return chain;
        }

    这就是整个资源调用链初始化的过程,也是基于单向链表的责任链模式的构建。

     
    
    
  • 相关阅读:
    dajngo 访问静态资源 图片
    springboot Shiro
    Jmeter集合点(线程组和同步定时器)
    Jmeter中的线程租的参数讲解
    人工智能必备数学知识学习笔记9:初等矩阵和矩阵的可逆性
    人工智能必备数学知识学习笔记8:线性系统
    在 web 中 后台 推送消息 页面 的消息提醒 注意
    web页面实现文件下载的几种方式
    ant desgin pro 的表格组件中的 使用 之自动刷新
    ant desgin 项目中 弹框modlel 模态框中展示 form 表单 将 form 表单 的提交按钮 关联到 模态框的 确定按钮上 在hook的写法中 在 class 的组件中 要加上 this
  • 原文地址:https://www.cnblogs.com/gaojy/p/15269664.html
Copyright © 2020-2023  润新知