• Sentinel笔记--Slotchain


     Sentinel 中的责任链模式

    Sentinel 中的 ProcessorSlot

    ProcessorSlot 直译就是处理器插槽,是 Sentinel 实现限流降级、熔断降级、系统自适应降级等功能的切入点。

    Sentinel 的核心骨架,将不同的 Slot 按照顺序串在一起(责任链模式),从而将不同的功能(限流、降级、系统保护)组合在一起。slot chain 其实可以分为两部分:统计数据构建部分(statistic)和判断部分(rule checking)。核心结构:

    sentinel-slot-chain

    目前的设计是 one slot chain per resource,因为某些 slot 是 per resource 的(比如 NodeSelectorSlot)。

      

    Sentinel 提供的 ProcessorSlot 可以分为两类,一类是辅助完成资源指标数据统计的切入点,一类是实现降级功能的切入点。

    辅助资源指标数据统计的 ProcessorSlot:
    • NodeSelectorSlot:为当前资源创建 DefaultNode,并且将 DefaultNode 赋值给 Context.curEntry.curNode;
      • 如果当前调用链路上只出现过一次 SphU#entry 的情况,将该 DefaultNode 添加到的 Context.entranceNode 的子节点,否则添加到 Context.curEntry.parent 的子节点(childList)。
    • ClusterBuilderSlot:如果当前资源未创建 ClusterNode,则为资源创建 ClusterNode;
      • 将 ClusterNode 赋值给当前资源的 DefaultNode.clusterNode;如果调用来源(origin)不为空,则为调用来源创建 StatisticNode,用于实现按调用来源统计资源的指标数据,ClusterNode 持有每个调用来源的 StatisticNode。
    • StatisticSlot:这是 Sentinel 最为重要的类之一,用于实现指标数据统计。先是调用后续的 ProcessorSlot#entry 判断是否放行请求,再根据判断结果进行相应的指标数据统计操作。

    这些辅助ProcessorSlot需要严格的顺序执行

    NodeSelectorSlot->ClusterBuilderSlot->StatisticSlot

     实现降级功能的 ProcessorSlot:
    • AuthoritySlot:实现黑白名单降级
    • SystemSlot:实现系统自适应降级
    • FlowSlot:实现限流降级
    • DegradeSlot:实现熔断降级 

    Sentinel 会为每个资源创建且仅创建一个 ProcessorSlotChain,只要名称相同就认为是同一个资源。ProcessorSlotChain 被缓存在 CtSph.chainMap 静态字段,key 为资源 ID.

     

    Sentinel 的整体工作流程

    如果不借助 Sentinel 提供的适配器,我们可以这样使用 Sentinel。 

    ContextUtil.enter("上下文名称,例如:sentinel_spring_web_context");
    Entry entry = null;
    try {
         entry = SphU.entry("资源名称,例如:/rpc/openfein/demo", EntryType.IN (或者 EntryType.OUT));
         // 执行业务方法
           return doBusiness();
    } catch (Exception e) {
         if (!(e instanceof BlockException)) {
              Tracer.trace(e);
         }
         throw e;
    } finally {
         if (entry != null) {
             entry.exit(1);
         }
         ContextUtil.exit();
    }

     此流程分5个部分

    1. 调用 ContextUtil#enter 方法;
    2. 调用 SphU#entry 方法;
    3. 如果抛出异常,且异常类型非 BlockException 异常,则调用 Tracer#trace 方法记录异常;
    4. 调用 Entry#exit 方法;
    5. 调用 ContextUtil#exit 方法。

     

    ContextUtil.enter流程

    ContextUtil#enter 方法负责为当前调用链路创建 Context,以及为 Conetxt 创建 EntranceNode
    private static volatile Map<String, DefaultNode> contextNameNodeMap = new HashMap<>();
    //资源入口,生成EntranceNode,如果没有Context的话,生成context并写入到ContextHolder中
    protected static Context trueEnter(String name, String origin) {
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
            //生成ResourceWrapper,生成EntranceNode
                node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                // Add entrance node.
                Constants.ROOT.addChild(node);
                Map<String, DefaultNode> newMap = new HashMap<>(contextNameNodeMap.size() + 1);
                newMap.putAll(contextNameNodeMap);
                newMap.put(name, node);
                contextNameNodeMap = newMap;
            }
            context = new Context(node, name);
            context.setOrigin(origin);
            contextHolder.set(context);
        }
        return context;
    }

    SphU.entry流程

    Sentinel 的核心骨架是 ProcessorSlotChain,所以核心的流程是一次 SphU#entry 方法的调用以及一次 CtEntry#exit 方法的调用。
    • SphU#entry 方法调用 CtSph#entry 方法,
    • CtSph 负责为资源创建 ResourceWrapper 对象并为资源构造一个全局唯一的 ProcessorSlotChain、
    • 为资源创建 CtEntry 并将 CtEntry 赋值给当前调用链路的 Context.curEntry、
    • 最后调用 ProcessorSlotChain#entry 方法完成一次单向链表的 entry 方法调用
    public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
       //生成resource
        StringResourceWrapper resource = new StringResourceWrapper(name, type);
        return entry(resource, count, args);
    }
    
    private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        Context context = ContextUtil.getContext();
        //核心,生成SlotChain
        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
        //生成entry
        Entry e = new CtEntry(resourceWrapper, chain, context);
        //开始处理具体逻辑
        chain.entry(context, resourceWrapper, null, count, prioritized, args);
        return e;
    }
    
    //生成SlotChain,默认使用SPI机制,加载
    ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
        ProcessorSlotChain chain = chainMap.get(resourceWrapper);
        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 Slot加载顺序如下

    # 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
     

    Entry#exit流程

    protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException {
        if (context != null) {
                //......
                // 1、调用 ProcessorSlotChain 的 exit 方法
                if (chain != null) {
                    chain.exit(context, resourceWrapper, count, args);
                }
                // 2、将当前 CtEntry 的父节点设置为 Context 的当前节点
                context.setCurEntry(parent);
                if (parent != null) {
                    ((CtEntry)parent).child = null;
                }
                // .....
        }
     }
     
    CtSph 在创建 CtEntry 时,将资源的 ProcessorSlotChain 赋值给了 CtEntry,所以在调用 CtEntry#exit 方法时,CtEntry 能够拿到当前资源的 ProcessorSlotChain,并调用 ProcessorSlotChain 的 exit 方法完成一次单向链表的 exit 方法调用。其过程与 ProcessorSlotChain 的一次 entry 方法的调用过程一样。
    CtEntry 在退出时还会还原 Context.curEntry。CtEntry 用于维护父子 Entry,每一次调用 SphU#entry 都会创建一个 CtEntry,如果应用处理一次请求的路径上会多次调用 SphU#entry,那么这些 CtEntry 会构成一个双向链表。在每次创建 CtEntry,都会将 Context.curEntry 设置为这个新的 CtEntry,双向链表的作用就是在调用 CtEntry#exit 方法时,能够将 Context.curEntry 还原为上一个 CtEntry。
     

    ContextUtil 的 exit 流程

    public static void exit() {
            Context context = contextHolder.get();
            if (context != null && context.getCurEntry() == null) {
                contextHolder.set(null);
            }
      }
     
    如果 Context.curEntry 为空,则说明所有 SphU#entry 都对应执行了一次 Entry#exit 方法,此时就可以将 Context 从 ThreadLocal 中移除。

    Plus:

    //所有树的根结点
    Constants.ROOT=new EntranceNode("machine-root",new ClusterNode("machine-root"));

    //为了SystemRule服务,用来统计全局metric时间窗口信息。
    Constants.ENTRY_NODE: new ClusterNode("__total_inbound_traffic__", 0)

    每次请求都会生成一个ResourceWrapper
    (res->ProcessorSlotChain)静态类,永驻内存。 最多6W个Chain,即最多6W个资源(接口)

    静态属性ContextUtil.contextNameNodeMap存储(contextName -> EntranceNode)
    静态属性CtSph.chainMap存储(res->ProcessorSlotChain)
    静态属性ClusterBuilderSlot.clusterNodeMap存储(res->clusterNode),
    实例属性clusterNode.originCountMap存储(origin->StatisticNode)


    默认Resource整个请求过程中,统计信息都放在clusterNode或者originNode中,
    具体统计计算是在StatisticNode中完成,rollingCounterInSecond只存1s数据,rollingCounterInMinute存储1分钟的数据。

    StatisticSlot

    StatisticSlot 是 Sentinel 最为重要的类之一,用于根据规则判断结果进行相应的统计操作。

    entry 的时候:依次执行后面的判断 slot。每个 slot 触发流控的话会抛出异常(BlockException 的子类)。若有 BlockException 抛出,则记录 block 数据;若无异常抛出则算作可通过(pass),记录 pass 数据。

    exit 的时候:若无 error(无论是业务异常还是流控异常),记录 complete(success)以及 RT,线程数-1。

    记录数据的维度:线程数+1、记录当前 DefaultNode 数据、记录对应的 originNode 数据(若存在 origin)、累计 IN 统计数据(若流量类型为 IN)。

  • 相关阅读:
    前端学习数据库之子元素
    最详细win7下手动搭建PHP环境:apache2.4.23+php7.0.11
    Javascript实现页面跳转的几种方式
    读书笔记:《HTML5开发手册》Web表单
    C#开发微信门户及应用(26)-公众号微信素材管理
    APP开发基础知识
    C#学习路线
    ASP.NET机制详细的管道事件流程
    SQL Serve中的6种事务隔离级别简单总结
    Asp.net设计模式笔记之一:理解设计模式
  • 原文地址:https://www.cnblogs.com/snow-man/p/15500847.html
Copyright © 2020-2023  润新知