sentinel源码分析-07GatewayFlowSlot网关限流

GatewayFlowSlot网关限流

sentinel支持api网关限流策略,其中包括 Spring Cloud Gateway。对于api网关限流,sentinel支持针对不同 route 或自定义的 API 分组进行限流,但是默认不支持 URL 粒度。

自定义分组可以通过api实现或通过dashboard页面配置,用户可以通过自定义分组将一些需要限流的url添加进来,支持精确匹配、前缀匹配、正则匹配,然后针对不同分组设置对应的限流规则,即可进行限流

通过api设置分组

Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition api1 = new ApiDefinition("some_customized_api")
            .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                add(new ApiPathPredicateItem().setPattern("/ahas"));
                add(new ApiPathPredicateItem().setPattern("/product/**")
                    .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
            }});
        ApiDefinition api2 = new ApiDefinition("another_customized_api")
            .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                add(new ApiPathPredicateItem().setPattern("/**")
                    .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
            }});
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);

这里设置了两个分组,并设置了对应匹配分组的规则

或者我们可以通dashboard设置分组,接口在GatewayApiController类,我们着重看一下保存逻辑
在这里插入图片描述

@PostMapping("/save.json")
    public Result<ApiDefinitionEntity> updateApi(HttpServletRequest request, @RequestBody UpdateApiReqVo reqVo) {
        AuthService.AuthUser authUser = authService.getAuthUser(request);

        String app = reqVo.getApp();
        if (StringUtil.isBlank(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }

        authUser.authTarget(app, AuthService.PrivilegeType.WRITE_RULE);

        Long id = reqVo.getId();
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        ApiDefinitionEntity entity = repository.findById(id);
        if (entity == null) {
            return Result.ofFail(-1, "api does not exist, id=" + id);
        }

        // 匹配规则列表
        List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
        if (CollectionUtils.isEmpty(predicateItems)) {
            return Result.ofFail(-1, "predicateItems can't empty");
        }

        List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
        for (ApiPredicateItemVo predicateItem : predicateItems) {
            ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();

            // 匹配模式
            int matchStrategy = predicateItem.getMatchStrategy();
            if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
                return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy);
            }
            predicateItemEntity.setMatchStrategy(matchStrategy);

            // 匹配串
            String pattern = predicateItem.getPattern();
            if (StringUtil.isBlank(pattern)) {
                return Result.ofFail(-1, "pattern can't be null or empty");
            }
            predicateItemEntity.setPattern(pattern);

            predicateItemEntities.add(predicateItemEntity);
        }
        entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));

        Date date = new Date();
        entity.setGmtModified(date);

        try {
            entity = repository.save(entity);
        } catch (Throwable throwable) {
            logger.error("update gateway api error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        if (!publishApis(app, entity.getIp(), entity.getPort())) {
            logger.warn("publish gateway apis fail after update");
        }

        return Result.ofSuccess(entity);
    }

这里可以看到就是针对不同属性的处理,然后将配置的自定义分组推送到客户端

private boolean publishApis(String app, String ip, Integer port) {
    List<ApiDefinitionEntity> apis = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    return sentinelApiClient.modifyApis(app, ip, port, apis);
}

 public boolean modifyApis(String app, String ip, int port, List<ApiDefinitionEntity> apis) {
    if (apis == null) {
        return true;
    }

    try {
        AssertUtil.notEmpty(app, "Bad app name");
        AssertUtil.notEmpty(ip, "Bad machine IP");
        AssertUtil.isTrue(port > 0, "Bad machine port");
        String data = JSON.toJSONString(
                apis.stream().map(r -> r.toApiDefinition()).collect(Collectors.toList()));
        Map<String, String> params = new HashMap<>(2);
        params.put("data", data);
        String result = executeCommand(app, ip, port, MODIFY_GATEWAY_API_PATH, params, true).get();
        logger.info("Modify gateway apis: {}", result);
        return true;
    } catch (Exception e) {
        logger.warn("Error when modifying gateway apis", e);
        return false;
    }
}

发送gateway/updateApiDefinitions请求,客户端会处理逻辑在UpdateGatewayApiDefinitionGroupCommandHandler

@Override
public CommandResponse<String> handle(CommandRequest request) {
    String data = request.getParam("data");
    if (StringUtil.isBlank(data)) {
        return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
    }
    try {
        data = URLDecoder.decode(data, "utf-8");
    } catch (Exception e) {
        RecordLog.info("Decode gateway API definition data error", e);
        return CommandResponse.ofFailure(e, "decode gateway API definition data error");
    }

    RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {0}", data);

    String result = SUCCESS_MSG;

    Set<ApiDefinition> apiDefinitions = parseJson(data);
    GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);

    return CommandResponse.ofSuccess(result);
}

这里解析页面下发的自定义分组配置然调用loadApiDefinitions,创建自定义分组然后保存到API_MAP中
在这里插入图片描述
同样页面在配置网关流控规则时会被客户端UpdateGatewayRuleCommandHandler类处理

@Override
public CommandResponse<String> handle(CommandRequest request) {
    String data = request.getParam("data");
    if (StringUtil.isBlank(data)) {
        return CommandResponse.ofFailure(new IllegalArgumentException("Bad data"));
    }
    try {
        data = URLDecoder.decode(data, "utf-8");
    } catch (Exception e) {
        RecordLog.info("Decode gateway rule data error", e);
        return CommandResponse.ofFailure(e, "decode gateway rule data error");
    }

    RecordLog.info(String.format("[API Server] Receiving rule change (type: gateway rule): %s", data));

    String result = SUCCESS_MSG;
    List<GatewayFlowRule> flowRules = JSONArray.parseArray(data, GatewayFlowRule.class);
    //设置网关流控规则
    GatewayRuleManager.loadRules(new HashSet<>(flowRules));
    return CommandResponse.ofSuccess(result);
}

GatewayRuleManager负责管理网关流控规则,通过loadRules保存网关流控规则,触发GatewayRulePropertyListener回调方法

@Override
public void configUpdate(Set<GatewayFlowRule> conf) {
    applyGatewayRuleInternal(conf);
    RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP);
}

@Override
public void configLoad(Set<GatewayFlowRule> conf) {
    applyGatewayRuleInternal(conf);
    RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + GATEWAY_RULE_MAP);
}

将网关流控规则转换为热点参数规则

private synchronized void applyGatewayRuleInternal(Set<GatewayFlowRule> conf) {
    //删除规则
    if (conf == null || conf.isEmpty()) {
        applyToConvertedParamMap(new HashSet<ParamFlowRule>());
        GATEWAY_RULE_MAP.clear();
        return;
    }
    //网关流控集合
    Map<String, Set<GatewayFlowRule>> gatewayRuleMap = new ConcurrentHashMap<>();
    //有参规则时候设置的下标
    Map<String, Integer> idxMap = new HashMap<>();
    //热点参数规则集合
    Set<ParamFlowRule> paramFlowRules = new HashSet<>();
    //存放没有设置热点参数的规则
    Map<String, List<GatewayFlowRule>> noParamMap = new HashMap<>();

    for (GatewayFlowRule rule : conf) {
        //校验规则失败直接跳过
        if (!isValidRule(rule)) {
            RecordLog.warn("[GatewayRuleManager] Ignoring invalid rule when loading new rules: " + rule);
            continue;
        }
        //资源名称
        String resourceName = rule.getResource();
        //没有热点参数
        if (rule.getParamItem() == null) {
            // Cache the rules with no parameter config, then skip.
            List<GatewayFlowRule> noParamList = noParamMap.get(resourceName);
            if (noParamList == null) {
                noParamList = new ArrayList<>();
                noParamMap.put(resourceName, noParamList);
            }
            noParamList.add(rule);
        } else {
            //每种资源对于热点参数索引值
            int idx = getIdxInternal(idxMap, resourceName);
            // Convert to parameter flow rule.
            //将gatewayRule转化为paramRule,并添加到热点参数规则集合中
            if (paramFlowRules.add(GatewayRuleConverter.applyToParamRule(rule, idx))) {
                //更新热点参数索引值
                idxMap.put(rule.getResource(), idx + 1);
            }
            cacheRegexPattern(rule.getParamItem());
        }
        // Apply to the gateway rule map.
        //保存所有的规则
        Set<GatewayFlowRule> ruleSet = gatewayRuleMap.get(resourceName);
        if (ruleSet == null) {
            ruleSet = new HashSet<>();
            gatewayRuleMap.put(resourceName, ruleSet);
        }
        ruleSet.add(rule);
    }
    // Handle non-param mode rules.
    //处理没有设置热点参数的规则
    for (Map.Entry<String, List<GatewayFlowRule>> e : noParamMap.entrySet()) {
        List<GatewayFlowRule> rules = e.getValue();
        if (rules == null || rules.isEmpty()) {
            continue;
        }
        for (GatewayFlowRule rule : rules) {
            int idx = getIdxInternal(idxMap, e.getKey());
            // Always use the same index (the last position).
            //同样也会转换为paramFlowRule,但是参数索引值相同
            paramFlowRules.add(GatewayRuleConverter.applyNonParamToParamRule(rule, idx));
        }
    }

    applyToConvertedParamMap(paramFlowRules);

    GATEWAY_RULE_MAP.clear();
    GATEWAY_RULE_MAP.putAll(gatewayRuleMap);
}

private void applyToConvertedParamMap(Set<ParamFlowRule> paramFlowRules) {
    Map<String, List<ParamFlowRule>> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap(
        new ArrayList<>(paramFlowRules));
    if (newRuleMap == null || newRuleMap.isEmpty()) {
        // No parameter flow rules, so clear all the metrics.
        //清空所有统计信息
        for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) {
            ParameterMetricStorage.clearParamMetricForResource(resource);
        }
        RecordLog.info("[GatewayRuleManager] No gateway rules, clearing parameter metrics of previous rules");
        CONVERTED_PARAM_RULE_MAP.clear();
        return;
    }

    // Clear unused parameter metrics.
    //删除无用的参数统计信息
    Set<String> previousResources = CONVERTED_PARAM_RULE_MAP.keySet();
    for (String resource : previousResources) {
        if (!newRuleMap.containsKey(resource)) {
            ParameterMetricStorage.clearParamMetricForResource(resource);
        }
    }

    // Apply to converted rule map.
    CONVERTED_PARAM_RULE_MAP.clear();
    CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap);

    RecordLog.info("[GatewayRuleManager] Converted internal param rules: " + CONVERTED_PARAM_RULE_MAP);
}

无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在 GatewayRuleManager 中,与正常的热点参数规则相隔离。转换时 Sentinel 会根据请求属性配置,为网关流控规则设置参数索引(idx),并同步到生成的热点参数规则中。

将有参网关限流规则转换为热点参数限流规则,如果pattern为空则针对所有热点参数都做流控,如果不为空则只有匹配的参数才做流控,否则直接放行

static ParamFlowRule applyToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
    ParamFlowRule paramRule = new ParamFlowRule(gatewayRule.getResource())
        .setCount(gatewayRule.getCount())
        .setGrade(gatewayRule.getGrade())
        .setDurationInSec(gatewayRule.getIntervalSec())
        .setBurstCount(gatewayRule.getBurst())
        .setControlBehavior(gatewayRule.getControlBehavior())
        .setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
        .setParamIdx(idx);
    //参数限流配置
    GatewayParamFlowItem gatewayItem = gatewayRule.getParamItem();
    // Apply the current idx to gateway rule item.
    //设置index下标,index自增,用户热点参数
    gatewayItem.setIndex(idx);
    // Apply for pattern-based parameters.
    // 针对有GatewayParamFlowItem.pattern
    String valuePattern = gatewayItem.getPattern();
    //设置例外
    if (valuePattern != null) {
        paramRule.getParamFlowItemList().add(generateNonMatchPassParamItem());
    }
    return paramRule;
}

将无参网关流控规则,转换为热点参数规则

static ParamFlowRule applyNonParamToParamRule(/*@Valid*/ GatewayFlowRule gatewayRule, int idx) {
    return new ParamFlowRule(gatewayRule.getResource())
        .setCount(gatewayRule.getCount())
        .setGrade(gatewayRule.getGrade())
        .setDurationInSec(gatewayRule.getIntervalSec())
        .setBurstCount(gatewayRule.getBurst())
        .setControlBehavior(gatewayRule.getControlBehavior())
        .setMaxQueueingTimeMs(gatewayRule.getMaxQueueingTimeoutMs())
        .setParamIdx(idx);
}

这里仅仅是属性的copy,无参网关流控规则最主要特点是,转换后热点规则的idx参数下标始终保持不变,是参数列表的最后一个元素。

网关接入限流规则通过SentinelGatewayFilter

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //路由
    Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

    Mono<Void> asyncResult = chain.filter(exchange);
    if (route != null) {
        //路由id
        String routeId = route.getId();
        //热点参数
        Object[] params = paramParser.parseParameterFor(routeId, exchange,
            r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        String origin = Optional.ofNullable(GatewayCallbackManager.getRequestOriginParser())
            .map(f -> f.apply(exchange))
            .orElse("");
        asyncResult = asyncResult.transform(
            new SentinelReactorTransformer<>(new EntryConfig(routeId, EntryType.IN,
                1, params, new ContextConfig(contextName(routeId), origin)))
        );
    }
    //匹配用户自定义的限流Api分组
    Set<String> matchingApis = pickMatchingApiDefinitions(exchange);
    for (String apiName : matchingApis) {
        //热点参数
        Object[] params = paramParser.parseParameterFor(apiName, exchange,
            r -> r.getResourceMode() == SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME);
        asyncResult = asyncResult.transform(
            new SentinelReactorTransformer<>(new EntryConfig(apiName, EntryType.IN, 1, params))
        );
    }

    return asyncResult;
}

解析获取热点参数

public Object[] parseParameterFor(String resource, T request, Predicate<GatewayFlowRule> rulePredicate) {
    if (StringUtil.isEmpty(resource) || request == null || rulePredicate == null) {
        return new Object[0];
    }
    Set<GatewayFlowRule> gatewayRules = new HashSet<>();
    Set<Boolean> predSet = new HashSet<>();
    boolean hasNonParamRule = false;
    for (GatewayFlowRule rule : GatewayRuleManager.getRulesForResource(resource)) {
        //设置了热点数据限流
        if (rule.getParamItem() != null) {
            gatewayRules.add(rule);
            predSet.add(rulePredicate.test(rule));
        } else {
            hasNonParamRule = true;
        }
    }
    if (!hasNonParamRule && gatewayRules.isEmpty()) {
        return new Object[0];
    }
    //route:规则中设置了针对Api分组
    if (predSet.size() > 1 || predSet.contains(false)) {
        return new Object[0];
    }
    int size = hasNonParamRule ? gatewayRules.size() + 1 : gatewayRules.size();
    //热点参数数组
    Object[] arr = new Object[size];
    for (GatewayFlowRule rule : gatewayRules) {
        GatewayParamFlowItem paramItem = rule.getParamItem();
        int idx = paramItem.getIndex();
        //解析获取参数值
        String param = parseInternal(paramItem, request);
        arr[idx] = param;
    }
    //最后一个设置默认值
    if (hasNonParamRule) {
        arr[size - 1] = SentinelGatewayConstants.GATEWAY_DEFAULT_PARAM;
    }
    return arr;
}

这里首先根据资源名称配合的route或者资源资源分组名称获取所有的网关流控规则,然后判断这些规则是否包含没有参数的规则,然后设置返回参数数组长度。解析设置的参数放入参数数组,如果有没有参数的规则,则将参数数组的最后一位设置成默认参数$D,为了进行资源的所有无参数例外项的热点规则流控校验

private String parseInternal(GatewayParamFlowItem item, T request) {
    switch (item.getParseStrategy()) {
        //请求源ip
        case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP:
            return parseClientIp(item, request);
            //host
        case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HOST:
            return parseHost(item, request);
            //请求header中取值
        case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER:
            return parseHeader(item, request);
            //请求url中取值
        case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM:
            return parseUrlParameter(item, request);
            //cookie中取值
        case SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE:
            return parseCookie(item, request);
        default:
            return null;
    }
}
private String parseClientIp(/*@Valid*/ GatewayParamFlowItem item, T request) {
    String clientIp = requestItemParser.getRemoteAddress(request);
    String pattern = item.getPattern();
    if (StringUtil.isEmpty(pattern)) {
        return clientIp;
    }
    return parseWithMatchStrategyInternal(item.getMatchStrategy(), clientIp, pattern);
}

private String parseWithMatchStrategyInternal(int matchStrategy, String value, String pattern) {
    if (value == null) {
        return null;
    }
    switch (matchStrategy) {
        //精确匹配
        case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_EXACT:
            return value.equals(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
            //子串
        case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS:
            return value.contains(pattern) ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
            //正则
        case SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX:
            Pattern regex = GatewayRegexCache.getRegexPattern(pattern);
            if (regex == null) {
                return value;
            }
            return regex.matcher(value).matches() ? value : SentinelGatewayConstants.GATEWAY_NOT_MATCH_PARAM;
        default:
            return value;
    }
}

解析参数支持请求源ip、host、header中取值、url中取值、cookie中取值。如果没有设置pattern则直接返回取值结果,如果设置pattern则可能是针对某类参数生效,支持3种模式精确匹配、子串匹配和正则匹配,如果匹配成功则返回参数值否则返回默认值$NM,它会匹配热点参数规则的参数例外项,但是例外项的阈值是100万,等同于直接放行

asyncResult = asyncResult.transform(
                new SentinelReactorTransformer<>(new EntryConfig(routeId, EntryType.IN,
                    1, params, new ContextConfig(contextName(routeId), origin)))
            );

@Override
    public Publisher<T> apply(Publisher<T> publisher) {
        if (publisher instanceof Mono) {
            return new MonoSentinelOperator<>((Mono<T>) publisher, entryConfig);
        }
        if (publisher instanceof Flux) {
            return new FluxSentinelOperator<>((Flux<T>) publisher, entryConfig);
        }

        throw new IllegalStateException("Publisher type is not supported: " + publisher.getClass().getCanonicalName());
    }

@Override
    public void subscribe(CoreSubscriber<? super T> actual) {
        source.subscribe(new SentinelReactorSubscriber<>(entryConfig, actual, true));
    }

最终都会走到SentinelReactorSubscriber拦截

@Override
protected void hookOnSubscribe(Subscription subscription) {
    doWithContextOrCurrent(() -> currentContext().getOrEmpty(SentinelReactorConstants.SENTINEL_CONTEXT_KEY),
        this::entryWhenSubscribed);
}

 private void entryWhenSubscribed() {
    //定义的配置
    ContextConfig sentinelContextConfig = entryConfig.getContextConfig();
    if (sentinelContextConfig != null) {
        // If current we're already in a context, the context config won't work.
        //初始化context
        ContextUtil.enter(sentinelContextConfig.getContextName(), sentinelContextConfig.getOrigin());
    }
    try {
        //执行限流逻辑
        AsyncEntry entry = SphU.asyncEntry(entryConfig.getResourceName(), entryConfig.getEntryType(),
            entryConfig.getAcquireCount(), entryConfig.getArgs());
        this.currentEntry = entry;
        actual.onSubscribe(this);
    } catch (BlockException ex) {
        // Mark as completed (exited) explicitly.
        entryExited.set(true);
        // Signal cancel and propagate the {@code BlockException}.
        cancel();
        actual.onSubscribe(this);
        actual.onError(ex);
    } finally {
        if (sentinelContextConfig != null) {
            ContextUtil.exit();
        }
    }
}

到这里就会进入sentinel的限流处理逻辑,进入GatewayFlowSlot处理

public class GatewayFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resource, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        checkGatewayParamFlow(resource, count, args);

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

    private void checkGatewayParamFlow(ResourceWrapper resourceWrapper, int count, Object... args)
        throws BlockException {
        if (args == null) {
            return;
        }
        //获取流控规则
        List<ParamFlowRule> rules = GatewayRuleManager.getConvertedParamRules(resourceWrapper.getName());
        if (rules == null || rules.isEmpty()) {
            return;
        }
		//和热点参数限流处理逻辑一样
        for (ParamFlowRule rule : rules) {
            // Initialize the parameter metrics.
            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);
            }
        }
    }

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

这里可以看到底层还是使用ParamFlowChecker.passCheck做热点参数规则校验。

  • 网关流控维度支持route或自定义分组,也就是资源名称配置支持路由或自定义分组名称
  • 无论是否针对请求属性进行限流,Sentinel 底层都会将网关流控规则转化为热点参数规则(ParamFlowRule),存储在 GatewayRuleManager 中
  • GatewayFlowRule中可以设置GatewayParamFlowItem,如果GatewayParamFlowItem为空则和普通流控规则相同,如果不为空比如设置了clientIp,再判断是否设置了匹配模式,如果设置了匹配模式则会根据匹配的clientIp进行统计限流,否则针对所有的clientIp进行统计限流。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值