ProcessorSlot

sentinel slot结构分析

关键特征:

  1. 责任链
  2. spi加载
  3. 子类抽象

接口定义

/**
 * 流程漕, 操作所在的处理容器
 *
 */
public interface ProcessorSlot<T> {
    /**
     * 实际执行的方法 
     */
    void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
               Object... args) throws Throwable;
    /*
     * 通常用做不同slot的流转
     */
    void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
                   Object... args) throws Throwable;
    /*
     * 退出当前slot
     */
    void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);

    /**
     * 完成退出之后实现的操作
     */
    void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
}

子类抽象类

AbstractLinkedProcessorSlot 使用该抽象类来向完成下一个slot流转的功能

public abstract class AbstractLinkedProcessorSlot<T> implements ProcessorSlot<T> {
    /*
     * 在nodeSelectSlot时,创建一条实际调用的单向链
     */
    private AbstractLinkedProcessorSlot<?> next = null;
    /*
     * 通过fireEntry 与 transformEntry 来控制slot一直往下流转
     */
    @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;
    }
}

子类实现类

子类实现类是通过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

上面的slot就是以顺序读取的形式,在lookProcesschain中,获取到chainlist,并且将其设置好调用顺序,类DefaultProcessorSlotChain 就是做这个事情的

public class DefaultProcessorSlotChain extends ProcessorSlotChain {
    // 会初始化2个slot,first 和 end,形成链表
    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;
        }
    }
    // 在尾部添加slot节点
    @Override
    public void addLast(AbstractLinkedProcessorSlot<?> protocolProcessor) {        
        end.setNext(protocolProcessor);
        end = protocolProcessor;
    }
    // 忽略下面代码
    ···
}

主要slot

  1. NodeSelectorSlot
@SpiOrder(-10000)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
    /**
     * {@link DefaultNode}s of the same resource in different context.
     */
    private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
    /**
     根据context 名称获取对应node,多线程环境下,由于map是线程私有,会在每一个线程创建对应DefaultNode
    */
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        DefaultNode node = map.get(context.getName());
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                
                if (node == null) {
                    node = new DefaultNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                    cacheMap.putAll(map);
                    cacheMap.put(context.getName(), node);
                    map = cacheMap;
                    // Build invocation tree
                    // 创建了一条调用树
                    // 每次不同的context 里面的相同的Node
                    // 然后在上一个node下面添加子node节点 类似:双向链表结构
                    ((DefaultNode) context.getLastNode()).addChild(node);
                }
            }
        }
        // 在这里保证当前的Node一定是最新的Node        
        context.setCurNode(node);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        fireExit(context, resourceWrapper, count, args);
    }
}

  1. ClusterBuilderSlot
    // 所有resource - clusterNode 的映射都会存在这里,因为culsterNode 需要统计所有context 所以要在这个slot里面保存处理
    // 一个资源只有一个clusterNode
    private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>();

    private static final Object lock = new Object();

    private volatile ClusterNode clusterNode = null;

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args)
        throws Throwable {
        if (clusterNode == null) {
            synchronized (lock) {
                if (clusterNode == null) {
                    // Create the cluster node.
        
                    clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
                    newMap.putAll(clusterNodeMap);
                    newMap.put(node.getId(), clusterNode);

                    clusterNodeMap = newMap;
                }
            }
        }
        // 设置ClusterNode
        node.setClusterNode(clusterNode);

        if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

  1. StatisticSlot

在Sphu调用链中已经有做分析,目前不额外展开,需要注意的是,这个slot会先等其他的slot走完 才会执行当前的slot的功能.主要功能还是2个统计


// 先走完其他的check slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);

// 1, 当前访问线程数+1
increaseThreadNum();
// 2. 已经通过的请求数+1 
addPassRequest(count);
  1. AuthoritySlot

这个slot 对应就是黑白名单来源访问规则,整个代码流程比较清晰

void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
        
        // 获取设定好的黑白名单set
        // 这个set 可以实时改动, 通过一个3s的定时线程池 拉取的所有sentinel Properties的改动,然后将改动发送到所有listener。
        // 观察者模式
        Map<String, Set<AuthorityRule>> authorityRules = AuthorityRuleManager.getAuthorityRules();

        if (authorityRules == null) {
            return;
        }
        // 根据resource 名称获取对应资源
        Set<AuthorityRule> rules = authorityRules.get(resource.getName());
        if (rules == null) {
            return;
        }    
        // 逐个规则过滤
        for (AuthorityRule rule : rules) {
            if (!AuthorityRuleChecker.passCheck(rule, context)) {
                throw new AuthorityException(context.getOrigin(), rule);
            }
        }
    }
  1. SystemSlot

该slot对应的就是系统保护规则, 在系统稳定的情况下,保持系统的吞吐量,保证不被拖垮. 从代码再看看其主要的影响模式

public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
        if (resourceWrapper == null) {
            return;
        }
        // Ensure the checking switch is on.
        if (!checkSystemStatus.get()) {
            return;
        }

        // for inbound traffic only 只看入站流量
        if (resourceWrapper.getEntryType() != EntryType.IN) {
            return;
        }

        //1. total qps
        double currentQps = Constants.ENTRY_NODE == null ? 0.0 : Constants.ENTRY_NODE.successQps();
        if (currentQps > qps) {
            throw new SystemBlockException(resourceWrapper.getName(), "qps");
        }

        //2. total thread
        int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
        if (currentThread > maxThread) {
            throw new SystemBlockException(resourceWrapper.getName(), "thread");
        }
           
        //3. 平均rt   
        double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
        if (rt > maxRt) {
            throw new SystemBlockException(resourceWrapper.getName(), "rt");
        }

        //4. load 自适应 load. BBR algorithm.
        if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
            if (!checkBbr(currentThread)) {
                throw new SystemBlockException(resourceWrapper.getName(), "load");
            }
        }

        //5.  cpu usage
        if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
            throw new SystemBlockException(resourceWrapper.getName(), "cpu");
        }
    }
  1. FlowSlot

这个slot 就是对应限流的相关规则,其属性有很多,我们将一些比较关键的代码过一下

  • 流量控制效果的区分,一般分为默认(就是直接限制),warmup 预热模式,RateLimit 匀速排队,不同的
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                          Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) {
            return;
        }
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
            for (FlowRule rule : rules) {
            //。在这里判断能不能通过
                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }
// 省略一部分代码,    rule 在这里的时候根据rule里面 controlBehavior 属性已经绑定了对应controller,来执行对应的方法
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
                                          boolean prioritized) {
        Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
        if (selectedNode == null) {
            return true;
        }
        // 直接执行选择的controlBehavior对应controller 的canPass方法,
        return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
    }    
    
// 特地说下warmupcontrller 有一个线性方程,我们可以写成y = m * x + b的形式,其中y(也就是y(x)),或qps(q)),是给定饱和期(例如3分钟)的预期qps, m是从我们的冷(最小)速率到我们的稳
// 定(最大)速率的变化率,x(或q)是已占用的令牌,来保证不超过其限制
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
        long passQps = (long) node.passQps();

        long previousQps = (long) node.previousPassQps();
        syncToken(previousQps);

        // 开始计算它的斜率
        // 如果进入了警戒线,开始调整他的qps
        long restToken = storedTokens.get();
        if (restToken >= warningToken) {
            long aboveToken = restToken - warningToken;
            // 消耗的速度要比warning快,但是要比慢
            // current interval = restToken*slope+1/count
            double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
            if (passQps + acquireCount <= warningQps) {
                return true;
            }
        } else {
            if (passQps + acquireCount <= count) {
                return true;
            }
        }

        return false;
    }
posted @ 2022-08-17 16:45  _我在清水河边  阅读(95)  评论(0编辑  收藏  举报