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)。

原文地址:https://www.cnblogs.com/snow-man/p/15500847.html