侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 20 个标签
  • 累计收到 3 条评论

Sentinel中的责任链模式

林贤钦
2021-10-30 / 0 评论 / 0 点赞 / 411 阅读 / 6,522 字
温馨提示:
本文最后更新于 2021-10-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Sentinel中的责任链模式

ProcessorSlot

ProcessorSlot是Sentinel实现限流降级,熔断降级,系统自适应等功能的切入点。Sentinel提供的ProcessorSlot分为两类,一类是辅助完成资源指标数据统计的切入点,一类是实现降级功能的切入点。

1、辅助资源指标数据统计的ProcessorSlot:

  • NodeSelectorSlot:为当前资源创建DefaultNode,并且将DefaultNode赋值到Context.curEntry.curNode;
  • ClusterBuilderSlot:如果当前资源未创建ClusterNode,则为资源创建ClusterNode;将ClusterNode赋值给当前资源的DefaultNode.clusterNode;
  • StatistiSlot:是sentinel最重要的一个类,用于实现指标统计。(先是调用后续的ProcessorSlot#entry判断是否放行请求,再根据判断结果进行相应的指标数据统计)

这三个在sentinel的责任链中的顺序是严格排序的,NodeSelectorSlot->ClusterBuilderSlot->StatisticSlot

2、实现降级功能的ProcessorSlot:

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

这几个顺序不要求,可以自定义实现降级功能重新排序。

ProcessorSlot 接口定义如下:

public interface ProcessorSlot<T> {
    // 入口方法 
    // context:当前调用链路上下文 | resourceWrapper:资源 ID,
    // param:泛型参数,一般用于传递 DefaultNode | count::Sentinel 将需要被保护的资源包装起来,这与锁的实现是一样的,需要先获取锁才能继续执行。
    // rioritized:表示是否对请求进行优先级排序,SphU#entry 传递过来的值是 false。
    // args:调用方法传递的参数,用于实现热点参数限流。
    void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,Object... args) throws Throwable;
    // 调用下一个 ProcessorSlot#entry 方法
    void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,Object... args) throws Throwable;
    // 出口方法
    void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
    // 调用下一个 ProcessorSlot#exit 方法
    void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}

例如实现熔断降级功能的 DegradeSlot,其在 entry 方法中检查资源当前统计的指标数据是否达到配置的熔断降级规则的阈值,如果是则触发熔断,抛出一个 DegradeException(必须是 BlockException 的子类),而 exit 方法什么也不做。

ProcessorSlotChain

ProcessorSlotChain是将ProcessorSlot 串成一个单向链表的,是由SlotChainBuilder构造的,默认的SlotChainBuilder构造ProcessorSlotChain 注册的 ProcessorSlot 以及顺序。

public class DefaultSlotChainBuilder implements SlotChainBuilder {

    @Override
    public ProcessorSlotChain build() {
        ProcessorSlotChain chain = new DefaultProcessorSlotChain();
		//加载spi的顺序,如果没有指定,以默认顺序实现
        List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
        for (ProcessorSlot slot : sortedSlotList) {
            if (!(slot instanceof AbstractLinkedProcessorSlot)) {
                continue;
            }
			//将加载的ProcessorSlot实现类注册到链表
            chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
        }

        return chain;
    }
}

之所以能够将所有的 ProcessorSlot 构造成一个 ProcessorSlotChain,还是依赖这些 ProcessorSlot 继承了 AbstractLinkedProcessorSlot 类。

AbstractLinkedProcessorSlot

每个 AbstractLinkedProcessorSlot 类都有一个指向下一个 AbstractLinkedProcessorSlot 的字段,正是这个字段将 ProcessorSlot 串成一条单向链表,实现责任链调用是由前一个 AbstractLinkedProcessorSlot 调用 fireEntry 方法或者 fireExit 方法,在 fireEntry 与 fireExit 方法中调用下一个 AbstractLinkedProcessorSlot(next)的 entry 方法或 exit 方法。AbstractLinkedProcessorSlot 部分源码如下。

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    // 当前节点的下一个节点
    private AbstractLinkedProcessorSlot<?> next = null;
    
    public void setNext(AbstractLinkedProcessorSlot<?> next) {
        this.next = next;
    }
    
    @Override
    public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        if (next != null) {
            // 调用下一个 ProcessorSlot 的 entry 方法
            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) {
            // 调用下一个 ProcessorSlot 的 exit 方法
            next.exit(context, resourceWrapper, count, args);
        }
    }
}

ProcessorSlotChain 也继承 AbstractLinkedProcessorSlot,只不过加了两个方法:提供将一个 ProcessorSlot 添加到链表的头节点的 addFirst 方法,以及提供将一个 ProcessorSlot 添加到链表末尾的 addLast 方法。

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;
    }
    //省略get、set next节点,重写entry和exit等代码
}

sentinel的责任链模式是以资源为维度,为每个资源创建且创建一个ProcessorSlotChain,只要名称相同就认为是同一个资源。

ProcessorSlotChain 被缓存在 CtSph.chainMap 静态字段,key 为资源 ID,每个资源的 ProcessorSlotChain 在 CtSph#entryWithPriority 方法中创建。

public class CtSph implements Sph {
    // 资源与 ProcessorSlotChain 的映射
    private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
        = new HashMap<ResourceWrapper, ProcessorSlotChain>();

   private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
        throws BlockException {
        Context context = ContextUtil.getContext();
        // ......
        // 开始构造 Chain ,判断chainMap是否存在该ProcessorSlot,不存在就新建添加
        ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
        //......
        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;
        }
        return e;
    }
}

entryWithPriority 在Sentinel 的整体工作流程中的entry = SphU.entry("资源名称", EntryType.IN (或者 EntryType.OUT));调用,实现链条的调用

0

评论区