sentinel

作者: 追风还是少年 | 来源:发表于2023-09-16 23:38 被阅读0次

    @SentinelResource属性:

    • value
      资源名称,默认为"",为""时,自动生成资源名,生成规则为className:methodName(paramName2,paramName2,...)
    • entryType
      entry类型,默认为EntryType.OUT(出口流量,即对本接口调用其它接口进行限流或熔断等),还可以设置为EntryType.IN(入口流量,即对外部应用调用本应用接口进行限流或熔断等)
    • resourceType
      默认为0,值枚举如下:
      0:COMMON
      1:COMMON_WEB
      2:COMMON_RPC
      3:COMMON_API_GATEWAY
      4:COMMON_DB_SQL
    • blockHandler
      对应处理 BlockException 的函数名称,默认为""
    • blockHandlerClass
      blockHandler函数所在的类,默认为{},只能提供一个类;如果提供blockHandlerClass,blockHandler函数必须是static方法。
    • fallback
      降级函数名称,默认为"",用于在抛出异常的时候提供 fallback 处理逻辑,fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
      (1)返回值类型必须与原函数返回值类型一致;
      (2)方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      (3)fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
    • defaultFallback
      通用的降级函数名称(即可以用于很多服务或方法),默认为"",默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效,defaultFallback 函数签名要求:
      (1)返回值类型必须与原函数返回值类型一致;
      方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
      (2)defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
    • fallbackClass
    • exceptionsToTrace
      指定哪些异常类需要计入异常统计,默认值为{Throwable.class}
    • exceptionsToIgnore
      用于指定哪些异常被排除掉,默认值为{},不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出,exceptionsToTrace与exceptionsToIgnore不应该同时出现,如果同时出现,exceptionsToIgnore优先级更高。

    若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出。

    public class TestService {
    
        // 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
        @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
        public void test() {
            System.out.println("Test");
        }
    
        // 原函数
        @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
        public String hello(long s) {
            return String.format("Hello at %d", s);
        }
        
        // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
        public String helloFallback(long s) {
            return String.format("Halooooo %d", s);
        }
    
        // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
        public String exceptionHandler(long s, BlockException ex) {
            // Do some log here.
            ex.printStackTrace();
            return "Oops, error occurred at " + s;
        }
    }
    

    规则

    image.png image.png image.png

    请求入口:
    SentinelResourceAspect#invokeResourceWithSentinel->SphU#entry->CtSph#entryWithType->CtSph#entryWithPriority

    public class CtSph implements Sph {
        private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
            throws BlockException {
            Context context = ContextUtil.getContext();
            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;
        }
    }
    

    核心类包括Entry、Node、Context:

    image.png

    Context

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

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

    Entry

    每一次资源调用都会创建一个 EntryEntry 包含了资源名、curNode(当前统计节点)、originNode(来源统计节点)等信息。

    CtEntry 为普通的 Entry,在调用 SphU.entry(xxx) 的时候创建。特性:Linked entry within current context(内部维护着 parentchild

    需要注意的一点:CtEntry 构造函数中会做调用链的变换,即将当前 Entry 接到传入 Context 的调用链路上(setUpEntryFor)。

    资源调用结束时需要 entry.exit()。exit 操作会过一遍 slot chain exit,恢复调用栈,exit context 然后清空 entry 中的 context 防止重复调用。

    Node

    Sentinel 里面的各种种类的统计节点:

    • StatisticNode:最为基础的统计节点,包含秒级和分钟级两个滑动窗口结构。
    • DefaultNode:链路节点,用于统计调用链路上某个资源的数据,维持树状结构。
    • ClusterNode:簇点,用于统计每个资源全局的数据(不区分调用链路),以及存放该资源的按来源区分的调用数据(类型为 StatisticNode)。特别地,Constants.ENTRY_NODE 节点用于统计全局的入口资源数据。
    • EntranceNode:入口节点,特殊的链路节点,对应某个 Context 入口的所有调用数据。Constants.ROOT 节点也是入口节点。

    构建的时机:

    • EntranceNode 在 ContextUtil.enter(xxx) 的时候就创建了,然后塞到 Context 里面。
    • NodeSelectorSlot:根据 context 创建 DefaultNode,然后 set curNode to context。
    • ClusterBuilderSlot:首先根据 resourceName 创建 ClusterNode,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode),并且 set originNode to curEntry。

    几种 Node 的维度(数目):

    • ClusterNode 的维度是 resource
    • DefaultNode 的维度是 resource * context,存在每个 NodeSelectorSlot 的 map 里面
    • EntranceNode 的维度是 context,存在 ContextUtil 类的 contextNameNodeMap 里面来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面
    image.png

    在NodeSelectorSlot中生成DefaultNode对象,通过context.setCurNode(node)设置CtEntry的curNode属性(DefaultNode),即先通过Context.curEntry获取当前Entry再设置。在传递该DefaultNode到下一个Slot,即ClusterBuilderSlot。
    在ClusterBuilderSlot中生成ClusterNode对象(只生成一次),把上一步DefaultNode的clusterNode设置为生成ClusterNode;根据context的origin生成StatisticNode对象,context.getCurEntry().setOriginNode(originNode)设置为该StatisticNode对象。

    StatisticSlot先传递到责任链上其后的Slot执行,没有Slot触发了阻止资源访问且其后所有Slot都执行完,才在StatisticSlot记录、统计指标监控信息的。


    image.png

    SphO

    SphO.entry方法返回false,代表超过规则阈值

     public void foo() {
        if (SphO.entry("abc")) {
            try {
                // business logic
            } finally {
                SphO.exit(); // must exit()
            }
        } else {
            // failed to enter the protected resource.
        }
     }
    

    SphU

     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();
            }
        }
     }
    

    ProcessorSlotChain

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

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

    slot在责任链中执行顺序是按照@SpiOrder定义,@SpiOrder对应得值越小越先执行。

    Resource/META-INF.services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder配置SPI加载哪个SlotChainBuilder实现类来组装责任链,默认为DefaultSlotChainBuilder。

    Resource/META-INF.services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot配置SPI加载哪些Slot实现类。

    同一个资源 ResourceWrapper (同一个资源名称) 会共同使用同一个 ProcessorSlotChain ,即不同的线程在访问同一个资源保护的代码时,这些线程将共同使用 ProcessorSlotChain 中的各个 ProcessorSlot。

    责任链表里的入口类是DefaultProcessorSlotChain,责任链执行过程:
    DefaultProcessorSlotChain#entry->
    (first)AbstractLinkedProcessorSlot.transformEntry->
    (first)AbstractLinkedProcessorSlot.entry->
    (first)AbstractLinkedProcessorSlot.fireEntry->
    (next)AbstractLinkedProcessorSlot.transformEntry->
    (next)AbstractLinkedProcessorSlot.entry->
    (next)AbstractLinkedProcessorSlot.fireEntry->
    (next.next)AbstractLinkedProcessorSlot.transformEntry->..

    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 {
            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);
        }
    
    }
    
    public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    
        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;
        }
    
    }
    
    image.png image.png

    相关文章

      网友评论

          本文标题:sentinel

          本文链接:https://www.haomeiwen.com/subject/ndjivdtx.html