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