sentinel源码分析第十篇一核心流程一ProcessorSlotChain.entry及ProcessorSlot链执行

原理图

官网的Slot链顺序存在问题,此处slot执行顺序同实际顺序

在这里插入图片描述

源码分析一入口CtSph.entryWithPriority

  • 通过DefaultProcessorSlotChain调用责任链
  • 所有的责任链工作完毕,发生限流则抛出异常
  • 未限流返回Entry栈链表
注意: 同一个资源在不同ContextName的上下文中,DefaultNode不同;
注意: 每一个资源只有一个ClusterNode 但可以有多个DefaultNode

try {
    每个ProcessorSlotentry()方法负责真正的业务处理部分
    NodeSelectorSlot 负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级
    ClusterBuilderSlot 则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;
    StatistcSlot 则用于记录,统计不同纬度的 runtime 信息;
    FlowSlot 则用于根据预设的限流规则,以及前面 slot 统计的状态,来进行限流;
    AuthorizationSlot 则根据黑白名单,来做黑白名单控制;
    DegradeSlot 则通过统计信息,以及预设的规则,来做熔断降级;
    SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;
     
    chain.entry(context, resourceWrapper, null, count, prioritized, args);
} catch (BlockException e1) {
    限流了,往上抛,这里exit了,而外界finally需要判空再exit
    e.exit(count, args);
    throw e1;
} catch (Throwable e1) {
    防御性编程  用来处理异常
    RecordLog.info("Sentinel unexpected exception", e1);
}

源码分析一NodeSelectorSlot

  • 负责构建限流链路
  • DefaultNode根据context.getName构建,也就是同一个资源可以对应多个DefaultNode
  • 构建下图关系
    -在这里插入图片描述
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
    private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10);
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {
        根据「上下文」的名称获取DefaultNode
        多线程环境下,每个线程都会创建一个context,但是一个contextName可能用于多个线程上下文
        DefaultNode node = map.get(context.getName());
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                if (node == null) {
                    如果当前「上下文」中没有该节点,则创建一个DefaultNode节点
                    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;
                    将当前node作为「上下文」的最后一个节点的子节点添加进去
                    如果context的curEntry.parent.curNode为null,则添加到entranceNode中去
                    否则添加到context的curEntry.parent.curNode中去
                    ((DefaultNode) context.getLastNode()).addChild(node);
                }

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

源码分析一ClusterBuilderSlot

  • 构建存储限流统计数据的clusterNode
  • 将clusterNode设置到限流链路当前的DefaultNode
  • 针对origin构建额外的StatisticNode
@SpiOrder(-9000)
public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    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) {
                    根据资源名构建clusterNode 也就是说一个资源一定对应且只能对应一个ClusterNode
                    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;
                }
            }
        }
        node.setClusterNode(clusterNode);

        如果context设置了Origin,则为origin创建单独的统计节点StatisticNode
        Origin一般用于区分环境,或者应用名,ip等,具体维度不定,比如区分不同的应用,则构建applicationname1,applicationname2作为Origin
        if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

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

源码分析一StatisticSlot

  • 先调用下游限流节点
  • 如果未被限流则执行数据统计计算工作
功能概述
负责资源限流数据的构建
线程: entry时增加线程数,exit 减少线程数,【不同于hystrix,其并没有通过线程池进行隔离,而是通过数值统计当前被使用的线程数,上下文切换更少】
qps: 只负责增加,通过样本窗口大小进行限流处理,exit无需减少

@SpiOrder(-7000)
实时统计的处理槽 
资源ID对应clusternode的统计
OriginNode对应的statisticsNode统计
EntryType.IN入站类型的额外统计
所有entranceNode的统计
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            【限流 降级 系统  授权等】规则检测 通过后正式统计
            检测完毕进行统计
            fireEntry(context, resourceWrapper, node, count, prioritized, args);

            通过DefaultNode为clusterNode增加线程数
            node.increaseThreadNum();
            增加qps count一般是1
            node.addPassRequest(count);

            if (context.getCurEntry().getOriginNode() != null) {
                设置origin则处理StatisticNode
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                全局统计添加节点添加线程数 qps
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest(count);
            }

            通过限流后则执行回调
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (PriorityWaitException ex) {
            node.increaseThreadNum();
            ...... 删除其他代码
            处理回调事件
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (BlockException e) {
            发生限流设置错误
            context.getCurEntry().setBlockError(e);
            被限流依旧添加qps
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                处理统计数据BlockQps
                Constants.ENTRY_NODE.increaseBlockQps(count);
            }

            处理回调
            for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }
            throw e;
        } catch (Throwable e) {
            context.getCurEntry().setError(e);
            throw e;
        }
    }

    @Override
    public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
        Node node = context.getCurNode();
        if (context.getCurEntry().getBlockError() == null) {
            step-1 计算rt响应时长
            long completeStatTime = TimeUtil.currentTimeMillis();
            context.getCurEntry().setCompleteTimestamp(completeStatTime);
            long rt = completeStatTime - context.getCurEntry().getCreateTimestamp();
            Throwable error = context.getCurEntry().getError();

            step-2 减少线程数 处理一些数据统计工作
            recordCompleteFor(node, count, rt, error);
            recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error);
            if (resourceWrapper.getEntryType() == EntryType.IN) {
                recordCompleteFor(Constants.ENTRY_NODE, count, rt, error);
            }
        }

        step-3 处理回调
        Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
        for (ProcessorSlotExitCallback handler : exitCallbacks) {
            handler.onExit(context, resourceWrapper, count, args);
        }

        fireExit(context, resourceWrapper, count);
    }

    private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) {
        if (node == null) {
            return;
        }
        node.addRtAndSuccess(rt, batchCount);
        减少线程数
        node.decreaseThreadNum();

        if (error != null && !(error instanceof BlockException)) {
            node.increaseExceptionQps(batchCount);
        }
    }
}

源码分析一SystemSlot

-通过SystemRuleManager检查系统限流

@SpiOrder(-5000)
public class SystemSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        检查系统规则是否触发限流 触发抛出异常
        SystemRuleManager.checkSystem(resourceWrapper);
        触发参数流控槽ParamFlowSlot执行
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
}

系统规则

检查维度
qps限流
线程数限流
基于平均rt 限流
系统负载过高并且线程数超过1,并且线程数超过平均qps
cpu使用率过高
public static void checkSystem(ResourceWrapper resourceWrapper) throws BlockException {
    if (resourceWrapper == null) {
        return;
    }
   
    if (!checkSystemStatus.get()) {
        return;
    }

     
    if (resourceWrapper.getEntryType() != EntryType.IN) {
        return;
    }

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

    线程数限流
    int currentThread = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.curThreadNum();
    if (currentThread > maxThread) {
        throw new SystemBlockException(resourceWrapper.getName(), "thread");
    }
    基于平均rt 限流
    double rt = Constants.ENTRY_NODE == null ? 0 : Constants.ENTRY_NODE.avgRt();
    if (rt > maxRt) {
        throw new SystemBlockException(resourceWrapper.getName(), "rt");
    }

    BBR 算法.
    系统负载过高
    if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
        线程数超过1,并且线程数超过平均qps
        if (!checkBbr(currentThread)) {
            throw new SystemBlockException(resourceWrapper.getName(), "load");
        }
    }

    cpu使用率过高
    if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) {
        throw new SystemBlockException(resourceWrapper.getName(), "cpu");
    }
}

源码分析一ParamFlowSlot

public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
                      热点参数规则管理器中不存在参数规则 不进行限流检查
        if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
            fireEntry(context, resourceWrapper, node, count, prioritized, args);
            return;
        }
        检查限流
        checkFlow(resourceWrapper, count, args);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
}

参数限流检查checkFlow

  • 根据资源名获取资源对应的限流规则
  • 初始化参数Metric限流统计数据容器
  • 如果检测不通过则抛出限流异常
  • 具体的检测算法参见下一篇文章
void checkFlow(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
    if (args == null) {
        return;
    }
    if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) {
        return;
    }
    // 根据资源名获取资源对应的限流规则
    List<ParamFlowRule> rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName());

    for (ParamFlowRule rule : rules) {
        applyRealParamIdx(rule, args.length);

        // 初始化参数Metric限流统计数据容器
        ParameterMetricStorage.initParamMetricsFor(resourceWrapper, rule);
        如果检测不通过则抛出限流异常
        if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) {
            String triggeredParam = "";
            if (args.length > rule.getParamIdx()) {
                Object value = args[rule.getParamIdx()];
                triggeredParam = String.valueOf(value);
            }
            throw new ParamFlowException(resourceWrapper.getName(), triggeredParam, rule);
        }
    }
}

源码分析一FlowSlot

  • 获取资源对应的规则
  • 通过规则检测器检测是否发生限流
  • 具体限流算法参见下一篇
@SpiOrder(-2000)
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    private final FlowRuleChecker checker;

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
         检查是否发生流控
        checkFlow(resourceWrapper, context, node, count, prioritized);
         流控之后触发降级
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
        throws BlockException {
        规则检测器检查是否发生限流
        checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
    }

   
}

源码分析一DegradeSlot

  • 获取所有的熔断器规则
  • 检测是否发生熔断
  • 发生熔断则报错
  • 具体算法参见下一篇
@SpiOrder(-1000)
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        熔断检查
        performChecking(context, resourceWrapper);
        结尾节点无下一个节点,结束责任链工作
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void performChecking(Context context, ResourceWrapper r) throws BlockException {
        获取熔断器规则
        List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
        if (circuitBreakers == null || circuitBreakers.isEmpty()) {
            return;
        }
        for (CircuitBreaker cb : circuitBreakers) {
            未通过熔断检测则熔断
            if (!cb.tryPass(context)) {
                throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
            }
        }
    }
}

总结

  • 概述了所有处理器槽的工作
  • 其中日志槽和授权槽相对简答,没有分析源码,一个负责日志,一个负责黑白名单拦截
  • 参数流控和资源流控以及熔断具体算法实现参见下一篇,包含了集群模式和单机模式的处理
  • 整个责任链先处理簇点链路以及统计数据节点的构成,随后触发限流规则检查,最后在StatisticSlot处完成数据统计
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值