@Author:zxw
@Email:502513206@qq.com


目录

  1. Sentinel源码分析(一) - 初识Sentinel
  2. Sentinel源码分析(二) - Entry构建
  3. Sentinel源码分析(三) - 调用链路

    1.前言

    通过上篇文章已经知道FlowSlot节点是用来处理我们流控的实现,以下为添加规则的示例代码

    1. private static void initFlowRules() {
    2. List<FlowRule> rules = new ArrayList<>();
    3. FlowRule rule = new FlowRule();
    4. // 资源名
    5. rule.setResource("HelloWorld");
    6. // 限流类型
    7. rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    8. // Set limit QPS to 20.
    9. // 限流阔值
    10. rule.setCount(1);
    11. rules.add(rule);
    12. FlowRuleManager.loadRules(rules);
    13. }

    以下是Sentinel控制台提供给我们的配置界面
    image.png

    2.源码分析

    2.1 FlowRule

    在查看Sentinel是如何实现流控之前,首先对FlowRule进行了解,单从实例代码来看一个FlowRule对象就代表一条规则,而FlowRuleManager则是我们规则的管理类了。先来看看Rule类的结构
    image.png
    可以发现有非常多的实现,不过对于流控的实现类则是FlowRule对象。在Sentinel中资源是一切的根本,所以在AbstractRule父类中定义了两个公共字段

    1. private String resource;
    2. private String limitApp;

    对于limitApp则是应用的来源,如果不存在的话,那么FlowRule会提供一个默认的来源

    1. public static final String LIMIT_APP_DEFAULT = "default";
    2. public FlowRule() {
    3. super();
    4. setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
    5. }

    此外,在Sentinel有两种限流的方式,分别为qps和线程数量,在FlowRule中使用grade字段进行标识,默认为qps限流。

    1. // RuleConstant
    2. public static final int FLOW_GRADE_THREAD = 0;
    3. public static final int FLOW_GRADE_QPS = 1;
    4. // FlowRule
    5. private int grade = RuleConstant.FLOW_GRADE_QPS;

    通过Sentinel控制台提供的功能来看,Sentinel有3种流控效果

  4. 直接失败

  5. warm up
  6. 排队等待
  7. 2与3的结合

默认情况,使用的是直接拒绝方式,通过controlBehavior字段标识

  1. public static final int CONTROL_BEHAVIOR_DEFAULT = 0;
  2. public static final int CONTROL_BEHAVIOR_WARM_UP = 1;
  3. public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2;
  4. public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3;
  5. private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;

配置了流控效果,那么就有不同的实现类去实现不同的流控策略,Sentinel通过TrafficShapingController接口来实现不同的流控策略。

  1. private TrafficShapingController controller;

所对应的实现则与上面配置的4个值相对应
image.png
既然是对流量进行控制,那么应该还需要记录当前流量值

  1. private double count;

以上就是FlowRule对象所涉及到的一些元数据,我们主要关注的字段有如下几个

  1. grade:配置qps或者线程数量模式
  2. controlBehavior:配置流控效果
  3. count:配置请求阈值

流控调度大概如下图
image.png

2.2 FlowRuleChecker

该类是规则检查类,所做的就是拿到该资源对应的所有规则,然后遍历并校验规则。

  1. public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
  2. Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
  3. if (ruleProvider == null || resource == null) {
  4. return;
  5. }
  6. Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
  7. if (rules != null) {
  8. for (FlowRule rule : rules) {
  9. if (!canPassCheck(rule, context, node, count, prioritized)) {
  10. throw new FlowException(rule.getLimitApp(), rule);
  11. }
  12. }
  13. }
  14. }

在取到每一个规则后,调用canPassCheck方法进行规则的校验,取到了资源的Node以后,获取到限流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;
        }

        return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
    }

3.限流算法

3.1 DefaultController(直接拒绝)

如果我们想使用并发线程数来进行流量控制,那么所使用的限流算法只能是DefaultController,因为只有在该类中对并发线程数进行了判断处理。
首先思考一个问题,要想知道当前是否需要限流是不是首先得知道当前系统的调用程度。那么这里就会用到qps或者线程数量

int curCount = avgUsedTokens(node);

private int avgUsedTokens(Node node) {
        if (node == null) {
            return DEFAULT_AVG_USED_TOKENS;
        }
        return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
}

如果当前请求数小于限制的数量总数,那么我们直接放行即可

if (curCount + acquireCount > count) {
    // .... 逻辑
    return false;
}
return true;

不过Sentinel内部还有一个针对请求优先级的算法,有兴趣的可以自己研究

if (curCount + acquireCount > count) {
            if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
                long currentTime;
                long waitInMs;
                currentTime = TimeUtil.currentTimeMillis();
                waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
                if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
                    node.addWaitingRequest(currentTime + waitInMs, acquireCount);
                    node.addOccupiedPass(acquireCount);
                    sleep(waitInMs);

                    // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
                    throw new PriorityWaitException(waitInMs);
                }
            }
            return false;
}

4.总结

这里只对直接拒绝策略的算法粗略的看了下,剩余其他算法感兴趣的可以直接研究,本篇主要分析了FlowRule的元数据结构