All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tencent.polaris.ratelimit.client.flow.QuotaFlow Maven / Gradle / Ivy

The newest version!
/*
 * Tencent is pleased to support the open source community by making Polaris available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.tencent.polaris.ratelimit.client.flow;

import com.tencent.polaris.api.config.provider.RateLimitConfig;
import com.tencent.polaris.api.control.Destroyable;
import com.tencent.polaris.api.exception.PolarisException;
import com.tencent.polaris.api.plugin.cache.FlowCache;
import com.tencent.polaris.api.plugin.compose.Extensions;
import com.tencent.polaris.api.plugin.ratelimiter.InitCriteria;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult.Code;
import com.tencent.polaris.api.plugin.registry.AbstractResourceEventListener;
import com.tencent.polaris.api.pojo.RegistryCacheValue;
import com.tencent.polaris.api.pojo.ServiceEventKey;
import com.tencent.polaris.api.pojo.ServiceEventKey.EventType;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.pojo.ServiceRule;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.MapUtils;
import com.tencent.polaris.api.utils.RuleUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.flow.BaseFlow;
import com.tencent.polaris.client.flow.ResourcesResponse;
import com.tencent.polaris.logging.LoggerFactory;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResponse;
import com.tencent.polaris.ratelimit.api.rpc.QuotaResultCode;
import com.tencent.polaris.ratelimit.client.pojo.CommonQuotaRequest;
import com.tencent.polaris.ratelimit.client.utils.RateLimitConstants;
import com.tencent.polaris.specification.api.v1.model.ModelProto.MatchString;
import com.tencent.polaris.specification.api.v1.model.ModelProto.MatchString.MatchStringType;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.MatchArgument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.RateLimit;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.Rule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.slf4j.Logger;

public class QuotaFlow extends Destroyable {

    private static final Logger LOG = LoggerFactory.getLogger(QuotaFlow.class);

    private RateLimitExtension rateLimitExtension;

    private RateLimitConfig rateLimitConfig;

    /**
     * 客户端的唯一标识
     */
    private String clientId;

    private boolean enabled;

    private final Map svcToWindowSet = new ConcurrentHashMap<>();

    public void init(Extensions extensions) throws PolarisException {
        clientId = extensions.getValueContext().getClientId();
        rateLimitExtension = new RateLimitExtension(extensions);
        rateLimitConfig = rateLimitExtension.getExtensions().getConfiguration().getProvider().getRateLimit();
        enabled = rateLimitConfig.isEnable();
        extensions.getLocalRegistry().registerResourceListener(new RateLimitRuleListener());
    }

    protected void doDestroy() {
        rateLimitExtension.destroy();
    }

    public QuotaResponse getQuota(CommonQuotaRequest request) throws PolarisException {
        if (!enabled) {
            return new QuotaResponse(
                    new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, RateLimitConstants.REASON_DISABLED));
        }
        List windows = lookupRateLimitWindow(request);
        if (CollectionUtils.isEmpty(windows)) {
            // 没有限流规则,直接放通
            return new QuotaResponse(
                    new QuotaResult(QuotaResult.Code.QuotaResultOk, 0, RateLimitConstants.REASON_RULE_NOT_EXISTS));
        }
        long maxWaitMs = 0;
        for (RateLimitWindow rateLimitWindow : windows) {
            rateLimitWindow.init();
            QuotaResponse quotaResponse = new QuotaResponse(rateLimitWindow.allocateQuota(request.getCount()));
            if (quotaResponse.getCode() == QuotaResultCode.QuotaResultLimited) {
                // 一个限流则直接限流
                // 记录这个限流 RateLimitWindow 对应的限流规则信息,放到 QuotaResponse 中返回
                quotaResponse.setActiveRule(rateLimitWindow.getRule());
                return quotaResponse;
            }
            if (quotaResponse.getWaitMs() > maxWaitMs) {
                maxWaitMs = quotaResponse.getWaitMs();
            }
        }
        return new QuotaResponse(new QuotaResult(Code.QuotaResultOk, maxWaitMs, ""));
    }

    private List lookupRateLimitWindow(CommonQuotaRequest request) throws PolarisException {
        //1.获取限流规则
        ResourcesResponse resourcesResponse = BaseFlow
                .syncGetResources(rateLimitExtension.getExtensions(), false, request, request.getFlowControlParam());
        ServiceRule serviceRule = resourcesResponse.getServiceRule(request.getSvcEventKey());
        //2.进行规则匹配
        List windows = new ArrayList<>();
        List rules = lookupRules(serviceRule, request.getMethod(), request.getArguments());
        if (CollectionUtils.isEmpty(rules)) {
            return windows;
        }
        ServiceKey serviceKey = request.getSvcEventKey().getServiceKey();
        for (Rule rule : rules) {
            InitCriteria initCriteria = new InitCriteria();
            initCriteria.setRule(rule);
            String labelsStr = formatLabelsToStr(request, initCriteria);
            //3.获取已有的限流窗口
            RateLimitWindowSet rateLimitWindowSet = getRateLimitWindowSet(serviceKey);
            RateLimitWindow rateLimitWindow = rateLimitWindowSet.getRateLimitWindow(rule, labelsStr);
            if (null != rateLimitWindow) {
                windows.add(rateLimitWindow);
            } else {
                //3.创建限流窗口
                windows.add(rateLimitWindowSet.addRateLimitWindow(request, labelsStr, rateLimitConfig, initCriteria));
            }

        }
        return windows;
    }

    private RateLimitWindowSet getRateLimitWindowSet(ServiceKey serviceKey) {
        RateLimitWindowSet rateLimitWindowSet = svcToWindowSet.get(serviceKey);
        if (null != rateLimitWindowSet) {
            return rateLimitWindowSet;
        }
        return svcToWindowSet.computeIfAbsent(serviceKey, new Function() {
            @Override
            public RateLimitWindowSet apply(ServiceKey serviceKey) {
                return new RateLimitWindowSet(serviceKey, rateLimitExtension, clientId);
            }
        });
    }

    private static String formatLabelsToStr(CommonQuotaRequest request, InitCriteria initCriteria) {
        Rule rule = initCriteria.getRule();
        MatchString method = rule.getMethod();
        boolean regexCombine = rule.getRegexCombine().getValue();
        String methodValue = "";
        if (null != method && !RuleUtils.isMatchAllValue(method)) {
            if (regexCombine && method.getType() != MatchStringType.EXACT) {
                methodValue = method.getValue().getValue();
            } else {
                methodValue = request.getMethod();
                if (method.getType() != MatchStringType.EXACT) {
                    //正则表达式扩散
                    initCriteria.setRegexSpread(true);
                }
            }
        }
        List argumentsList = rule.getArgumentsList();
        List tmpList = new ArrayList<>();
        Map> arguments = request.getArguments();
        for (MatchArgument matchArgument : argumentsList) {
            String labelValue;
            MatchString matcher = matchArgument.getValue();
            if (regexCombine && matcher.getType() != MatchStringType.EXACT) {
                labelValue = matcher.getValue().getValue();
            } else {
                Map stringStringMap = arguments.get(matchArgument.getType().ordinal());
                labelValue = getLabelValue(matchArgument, stringStringMap);
                if (matcher.getType() != MatchStringType.EXACT) {
                    //正则表达式扩散
                    initCriteria.setRegexSpread(true);
                }
            }
            String labelEntry = getLabelEntry(matchArgument, labelValue);
            if (StringUtils.isNotBlank(labelEntry)) {
                tmpList.add(labelEntry);
            }
        }
        Collections.sort(tmpList);
        return methodValue + RateLimitConstants.DEFAULT_ENTRY_SEPARATOR + String
                .join(RateLimitConstants.DEFAULT_ENTRY_SEPARATOR, tmpList);
    }

    private static String getLabelEntry(MatchArgument matchArgument, String labelValue) {
        switch (matchArgument.getType()) {
            case CUSTOM:
            case HEADER:
            case QUERY:
            case CALLER_SERVICE: {
                return matchArgument.getType().name() + RateLimitConstants.DEFAULT_KV_SEPARATOR + matchArgument.getKey()
                        + RateLimitConstants.DEFAULT_KV_SEPARATOR + labelValue;
            }
            case METHOD:
            case CALLER_IP: {
                return matchArgument.getType().name() + RateLimitConstants.DEFAULT_KV_SEPARATOR + labelValue;
            }
            default:
                return "";
        }
    }

    private static String getLabelValue(MatchArgument matchArgument,
            Map stringStringMap) {
        switch (matchArgument.getType()) {
            case CUSTOM:
            case HEADER:
            case QUERY:
            case CALLER_SERVICE: {
                return stringStringMap.get(matchArgument.getKey());
            }
            case METHOD:
            case CALLER_IP: {
                return stringStringMap.values().iterator().next();
            }
            default:
                return stringStringMap.get(matchArgument.getKey());
        }
    }

    private List lookupRules(ServiceRule serviceRule, String method,
            Map> arguments) {
        if (null == serviceRule || null == serviceRule.getRule()) {
            return null;
        }
        RateLimit rateLimitProto = (RateLimit) serviceRule.getRule();
        List rulesList = rateLimitProto.getRulesList();
        if (CollectionUtils.isEmpty(rulesList)) {
            return null;
        }
        Function function = regex -> {
            FlowCache flowCache = rateLimitExtension.getExtensions().getFlowCache();
            return flowCache.loadOrStoreCompiledRegex(regex);
        };
        List matchRules = new ArrayList<>();
        for (Rule rule : rulesList) {
            if (Objects.nonNull(rule.getDisable()) && rule.getDisable().getValue()) {
                LOG.debug("rule(id={}, name={}, revision={}) disable open, ignore", rule.getId().getValue(),
                        rule.getName().getValue(), rule.getRevision().getValue());
                continue;
            }
            if (rule.getAmountsCount() == 0) {
                //没有amount的规则就忽略
                LOG.debug("rule(id={}, name={}, revision={}) amounts count is zero, ignore", rule.getId().getValue(),
                        rule.getName().getValue(), rule.getRevision().getValue());
                continue;
            }
            //match method
            MatchString methodMatcher = rule.getMethod();
            if (Objects.nonNull(methodMatcher)) {
                boolean matchMethod = RuleUtils.matchStringValue(methodMatcher, method, function);
                if (!matchMethod) {
                    continue;
                }
            }
            List argumentsList = rule.getArgumentsList();
            boolean matched = true;
            if (CollectionUtils.isNotEmpty(argumentsList)) {
                for (MatchArgument matchArgument : argumentsList) {
                    Map stringStringMap = arguments.get(matchArgument.getType().ordinal());
                    if (CollectionUtils.isEmpty(stringStringMap)) {
                        matched = false;
                        break;
                    }
                    String labelValue = getLabelValue(matchArgument, stringStringMap);
                    if (null == labelValue) {
                        matched = false;
                    } else {
                        matched = RuleUtils.matchStringValue(matchArgument.getValue(), labelValue, function);
                    }
                    if (!matched) {
                        break;
                    }
                }
            }
            if (matched) {
                matchRules.add(rule);
            }
        }
        return matchRules;
    }

    private static Map parseRules(RegistryCacheValue oldValue) {
        if (null == oldValue || !oldValue.isInitialized()) {
            return null;
        }
        ServiceRule serviceRule = (ServiceRule) oldValue;
        if (null == serviceRule.getRule()) {
            return null;
        }
        Map ruleMap = new HashMap<>();
        RateLimit rateLimit = (RateLimit) serviceRule.getRule();
        for (Rule rule : rateLimit.getRulesList()) {
            ruleMap.put(rule.getRevision().getValue(), rule);
        }
        return ruleMap;
    }

    private void deleteRules(ServiceKey serviceKey, Set deletedRules) {
        LOG.info("[RateLimit]start to delete rules {} for service {}", deletedRules, serviceKey);
        RateLimitWindowSet rateLimitWindowSet = svcToWindowSet.get(serviceKey);
        if (null == rateLimitWindowSet) {
            return;
        }
        rateLimitWindowSet.deleteRules(deletedRules);
    }

    private class RateLimitRuleListener extends AbstractResourceEventListener {


        @Override
        public void onResourceUpdated(ServiceEventKey svcEventKey, RegistryCacheValue oldValue,
                RegistryCacheValue newValue) {
            EventType eventType = svcEventKey.getEventType();
            if (eventType != EventType.RATE_LIMITING) {
                return;
            }
            Map oldRules = parseRules(oldValue);
            Map newRules = parseRules(newValue);
            if (MapUtils.isEmpty(oldRules)) {
                return;
            }
            Set deletedRules = new HashSet<>();
            for (Map.Entry entry : oldRules.entrySet()) {
                if (MapUtils.isEmpty(newRules) || !newRules.containsKey(entry.getKey())) {
                    deletedRules.add(entry.getKey());
                }
            }
            if (CollectionUtils.isNotEmpty(deletedRules)) {
                deleteRules(svcEventKey.getServiceKey(), deletedRules);
            }
        }

        @Override
        public void onResourceDeleted(ServiceEventKey svcEventKey, RegistryCacheValue oldValue) {
            EventType eventType = svcEventKey.getEventType();
            if (eventType != EventType.RATE_LIMITING) {
                return;
            }
            Map oldRules = parseRules(oldValue);
            if (MapUtils.isEmpty(oldRules)) {
                return;
            }
            deleteRules(svcEventKey.getServiceKey(), oldRules.keySet());
        }
    }

}






© 2015 - 2024 Weber Informatics LLC | Privacy Policy