sentinel源码分析第十一篇一核心流程一ParamFlowSlot流控

限流实现一ParamFlowSlot

在这里插入图片描述

ParamFlowChecker.passCheck

  • 方法签名的参数个数小于配置的热点限流位置不进行限流
  • 获取限流参数值
  • 如果入参是一个ParamFlowArgument类型 修改参数值为paramFlowKey()返回值
  • 无值填充则不进行限流
  • 集群qps限流
  • 单机限流
 public final class ParamFlowChecker {

     public static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count,
                                 Object... args) {
        方法签名的参数个数小于配置的热点限流位置不进行限流
        int paramIdx = rule.getParamIdx();
        if (args.length <= paramIdx) {
            return true;
        }
        获取限流的值 【sentinel目前只支持对基本参数类型外加String进行限流】
        Object value = args[paramIdx];

        如果入参是一个ParamFlowArgument类型 则根据其的paramFlowKey获取的值进行限流
        使用场景:入参是某个复合对象 比如一个用户[user_id,user_name] user_name有重复,而我们只想使用user_id限流,此时使User 实现ParamFlowArgument,覆写paramFlowKey方法
        if (value instanceof ParamFlowArgument) {
            value = ((ParamFlowArgument) value).paramFlowKey();
        }
        无值填充则不进行限流
        if (value == null) {
            return true;
        }
        集群检测 qps限流
        if (rule.isClusterMode() && rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
            return passClusterCheck(resourceWrapper, rule, count, value);
        }
       单机检测
        return passLocalCheck(resourceWrapper, rule, count, value);
    }
}

集群模式一passClusterCheck

  • 获取TokenService,作为嵌入式节点,其可能本身就是server,也可能是client
  • 如果没有配置集群的server和client节点,则降级
  • client远程调用server获取限流结果
  • TokenService如果是server直接调用内部算法进行限流判断
 private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
    try {
        Collection<Object> params = toCollection(value);
        获取server或者client实例 如果未配置则降级成直接通过或者单机限流模式
        TokenService clusterService = pickClusterService();
        if (clusterService == null) {
            管控台没有配置集群模式,或者配置的集群不包含当前节点信息
            return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
        }
        TokenResult result = clusterService.requestParamToken(rule.getClusterConfig().getFlowId(), count, params);
        switch (result.getStatus()) {
            case TokenResultStatus.OK:
                return true;
            case TokenResultStatus.BLOCKED:
                return false;
            default:
                
                根据配置直接通过限流或者退化到 local 模式的限流
                return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
        }
    } catch (Throwable ex) {
        若用户未引入集群限流 client 相关依赖,或者 client未开启/连接失败/通信失败
        根据配置直接通过限流或者退化到 local 模式的限流
        return fallbackToLocalOrPass(resourceWrapper, rule, count, value);
    }
}
获取限流信息一requestParamToken

如果节点是server节点,执行如下,如果是client节点则通过通信调用以下代码

  • 通过tokenserver调用ClusterFlowChecker进行限流
public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer {
    
   public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
        if (tokenService != null) {
            调用DefaultTokenService进行限流
            return tokenService.requestParamToken(ruleId, acquireCount, params);
        }
        return new TokenResult(TokenResultStatus.FAIL);
    }
}

public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
    if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) {
        return badRequest();
    }
   
    ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId);
    if (rule == null) {
        return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
    }
    调用ClusterFlowChecker进行窗口限流
    return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
}
  
限流实现一ClusterFlowChecker
public final class ClusterParamFlowChecker {
    static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection<Object> values) {
        Long id = rule.getClusterConfig().getFlowId();
        // 保护机制
        if (!allowProceed(id)) {
            return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
        }
        // 获取度量信息
        ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id);
        if (metric == null) {
            不存在直接返回fail
            return new TokenResult(TokenResultStatus.FAIL);
        }
        if (values == null || values.isEmpty()) {
            无参直接通过限流
            return new TokenResult(TokenResultStatus.OK);
        }
        double remaining = -1;
        boolean hasPassed = true;
        Object blockObject = null;
        只要有一个值没有通过限流检查则限流
        for (Object value : values) {
            double latestQps = metric.getAvg(value);
            没有配置参数级别则使用总的阈值
            double threshold = calcGlobalThreshold(rule, value);
            double nextRemaining = threshold - latestQps - count;
            remaining = nextRemaining;
            if (nextRemaining < 0) {
                hasPassed = false;
                blockObject = value;
                break;
            }
        }
        通过限流增加统计数据
        if (hasPassed) {
            for (Object value : values) {
                metric.addValue(value, count);
            }
            ClusterServerStatLogUtil.log(String.format("param|pass|%d", id));
        } else {
            ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject));
        }
        if (values.size() > 1) {
            // Remaining field is unsupported for multi-values.
            remaining = -1;
        }
        返回限流结果
        return hasPassed ? newPassResponse((int)remaining): newBlockResponse();
    }
}

单机模式一passLocalCheck

  • 判断qps还是thread数限流
  • qps限流: 流控效果为排队等待执行passThrottleLocalCheck
  • qps限流: 流控效果不为排队等待执行passDefaultLocalCheck

passDefaultLocalCheck引入令牌桶,为令牌桶实现,这里不再叙述

private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
    ......删除其他代码    
    单机检测
    if (!passSingleValueCheck(resourceWrapper, rule, count, param)) {
        return false;
    }
    return true;
}


static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount,
                                        Object value) {
    如果是qps限流
    if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
        流控效果: 排队等待
        if (rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) {
            如果没有获取则在统计窗口时间范围内排队获取
            return passThrottleLocalCheck(resourceWrapper, rule, acquireCount, value);
        } else {
            流控效果:
            return passDefaultLocalCheck(resourceWrapper, rule, acquireCount, value);
        }
    如果是线程数限流
    } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) {
        Set<Object> exclusionItems = rule.getParsedHotItems().keySet();
        获取线程数
        long threadCount = getParameterMetric(resourceWrapper).getThreadCount(rule.getParamIdx(), value);
        热点参数线程数比较
        if (exclusionItems.contains(value)) {
            int itemThreshold = rule.getParsedHotItems().get(value);
            return ++threadCount <= itemThreshold;
        }
        普通参数线程数比较
        long threshold = (long)rule.getCount();
        return ++threadCount <= threshold;
    }
    return true;
}
passThrottleLocalCheck
  • 获取度量数据[限流统计数据]
  • 如果当前参数值匹配了指定值 则根据指定值配置的限流信息进行限流
  • 在一个配置窗口时间段内可以不断等待超过窗口则返回false
  • 如果需要等待则执行线程休眠waitTime
 static boolean passThrottleLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int acquireCount,
                                          Object value) {
    获取度量数据[限流统计数据]
    ParameterMetric metric = getParameterMetric(resourceWrapper);
    获取 规则对应的限流数据
    Object为方法入参参数值, AtomicLong为参数值对应的限流qps
    CacheMap<Object, AtomicLong> timeRecorderMap = metric == null ? null : metric.getRuleTimeCounter(rule);
    if (timeRecorderMap == null) {
        return true;
    }

    // Calculate max token count (threshold)
    Set<Object> exclusionItems = rule.getParsedHotItems().keySet();
    long tokenCount = (long)rule.getCount();
    如果当前参数值匹配了指定值 则根据指定值配置的限流信息进行限流
    if (exclusionItems.contains(value)) {
        tokenCount = rule.getParsedHotItems().get(value);
    }
    if (tokenCount == 0) {
        return false;
    }

    获取的数量 * 统计窗口的时长/ 规则配置阈值 * 1000[秒转毫秒]
    在一个配置窗口时间段内可以不断等待超过窗口则返回false
    long costTime = Math.round(1.0 * 1000 * acquireCount * rule.getDurationInSec() / tokenCount);
    while (true) {
        long currentTime = TimeUtil.currentTimeMillis();
        AtomicLong timeRecorder = timeRecorderMap.putIfAbsent(value, new AtomicLong(currentTime));
        if (timeRecorder == null) {
            return true;
        }
        //AtomicLong timeRecorder = timeRecorderMap.get(value);
        long lastPassTime = timeRecorder.get();
        排队上限时间
        long expectedTime = lastPassTime + costTime;

        if (expectedTime <= currentTime || expectedTime - currentTime < rule.getMaxQueueingTimeMs()) {
            更新排队时间上限
            AtomicLong lastPastTimeRef = timeRecorderMap.get(value);
            if (lastPastTimeRef.compareAndSet(lastPassTime, currentTime)) {
                最多还可以等多久
                long waitTime = expectedTime - currentTime;
                if (waitTime > 0) {
                    lastPastTimeRef.set(expectedTime);
                    try {
                        直接让出cpu
                        TimeUnit.MILLISECONDS.sleep(waitTime);
                    } catch (InterruptedException e) {
                        RecordLog.warn("passThrottleLocalCheck: wait interrupted", e);
                    }
                }
                return true;
            } else {
                cas 失败重新计算
                Thread.yield();
            }
        } else {
            超过排队时间直接报错
            return false;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值