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

com.tencent.polaris.ratelimit.client.flow.RateLimitWindow 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.plugin.ratelimiter.InitCriteria;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaBucket;
import com.tencent.polaris.api.plugin.ratelimiter.QuotaResult;
import com.tencent.polaris.api.plugin.ratelimiter.ServiceRateLimiter;
import com.tencent.polaris.api.pojo.DefaultInstance;
import com.tencent.polaris.api.pojo.DefaultServiceInstances;
import com.tencent.polaris.api.pojo.Instance;
import com.tencent.polaris.api.pojo.ServiceInstances;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import com.tencent.polaris.client.flow.FlowControlParam;
import com.tencent.polaris.logging.LoggerFactory;
import com.tencent.polaris.ratelimit.client.pojo.CommonQuotaRequest;
import com.tencent.polaris.ratelimit.client.sync.RemoteSyncTask;
import com.tencent.polaris.ratelimit.client.utils.RateLimitConstants;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.Amount;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.RateLimitCluster;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto.Rule;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;

/**
 * 限流窗口
 */
public class RateLimitWindow {

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

    public enum WindowStatus {
        /**
         * 刚创建, 无需进行后台调度
         */
        CREATED,
        /**
         * 已获取调度权,准备开始调度
         */
        INITIALIZING,
        /**
         * 已经在远程初始化结束
         */
        INITIALIZED,
        /**
         * 已经删除
         */
        DELETED,
    }

    private final RateLimitWindowSet windowSet;

    private final ServiceKey svcKey;

    private final String labels;

    private final String uniqueKey;

    private final int hashValue;

    private final FlowControlParam syncParam;

    private final Object initLock = new Object();

    private final AtomicInteger status = new AtomicInteger();

    private final AtomicLong lastAccessTimeMs = new AtomicLong();

    // 执行正式分配的令牌桶
    private final QuotaBucket allocatingBucket;

    private final long expireDurationMs;

    //远程同步的集群名
    private final ServiceKey remoteCluster;

    //限流的服务端地址列表
    private final ServiceInstances remoteAddresses;

    //限流规则
    private final Rule rule;

    //限流模式
    private int configMode;

    //限流配置
    private final RateLimitConfig rateLimitConfig;

    /**
     * 构造函数
     *
     * @param windowSet 集合
     * @param quotaRequest 请求
     * @param labelsStr 标签
     */
    public RateLimitWindow(RateLimitWindowSet windowSet, CommonQuotaRequest quotaRequest, String labelsStr,
            RateLimitConfig rateLimitConfig, InitCriteria initCriteria) {
        status.set(WindowStatus.CREATED.ordinal());
        Rule rule = initCriteria.getRule();
        this.rule = rule;
        this.windowSet = windowSet;
        this.svcKey = quotaRequest.getSvcEventKey().getServiceKey();
        this.labels = labelsStr;
        this.uniqueKey = buildQuotaUniqueKey(rule.getRevision().getValue());
        initCriteria.setWindowKey(this.uniqueKey);
        this.hashValue = uniqueKey.hashCode();
        this.expireDurationMs = getExpireDurationMs(rule);
        this.syncParam = quotaRequest.getFlowControlParam();
        remoteCluster = getLimiterClusterService(rule.getCluster(), rateLimitConfig);
        remoteAddresses = buildDefaultInstances(rateLimitConfig.getLimiterAddresses());
        allocatingBucket = getQuotaBucket(initCriteria, windowSet.getRateLimitExtension());
        lastAccessTimeMs.set(System.currentTimeMillis());
        this.rateLimitConfig = rateLimitConfig;
        buildRemoteConfigMode();
    }

    private ServiceInstances buildDefaultInstances(List addresses) {
        if (CollectionUtils.isEmpty(addresses)) {
            return null;
        }
        List instances = new ArrayList<>();
        for (String address : addresses) {
            DefaultInstance defaultInstance = new DefaultInstance();
            defaultInstance.setId(address);
            String[] tokens = address.split(":");
            defaultInstance.setHost(tokens[0]);
            defaultInstance.setPort(Integer.parseInt(tokens[1]));
            defaultInstance.setHealthy(true);
            defaultInstance.setWeight(100);
            instances.add(defaultInstance);
        }
        return new DefaultServiceInstances(new ServiceKey("", ""), instances);


    }

    private ServiceKey getLimiterClusterService(RateLimitCluster cluster, RateLimitConfig rateLimitConfig) {
        if (null != cluster && StringUtils.isNotBlank(cluster.getNamespace().getValue()) && StringUtils
                .isNotBlank(cluster.getService().getValue())) {
            return new ServiceKey(cluster.getNamespace().getValue(), cluster.getService().getValue());
        }
        if (StringUtils.isNotBlank(rateLimitConfig.getLimiterNamespace()) && StringUtils
                .isNotBlank(rateLimitConfig.getLimiterService())) {
            return new ServiceKey(rateLimitConfig.getLimiterNamespace(), rateLimitConfig.getLimiterService());
        }
        return null;
    }


    private void buildRemoteConfigMode() {
        //解析限流集群配置
        if (Rule.Type.LOCAL.equals(rule.getType())) {
            this.configMode = RateLimitConstants.CONFIG_QUOTA_LOCAL_MODE;
            return;
        }
        if (null == remoteCluster && null == remoteAddresses) {
            this.configMode = RateLimitConstants.CONFIG_QUOTA_LOCAL_MODE;
            LOG.warn("remote limiter service or addresses not configured, degrade to local mode");
            return;
        }
        this.configMode = RateLimitConstants.CONFIG_QUOTA_GLOBAL_MODE;
    }

    public String getUniqueKey() {
        return uniqueKey;
    }

    private static QuotaBucket getQuotaBucket(InitCriteria criteria, RateLimitExtension extension) {
        String action = criteria.getRule().getAction().getValue();
        ServiceRateLimiter rateLimiter = null;
        if (StringUtils.isNotBlank(action)) {
            rateLimiter = extension.getRateLimiter(action);
        }
        if (null == rateLimiter) {
            rateLimiter = extension.getDefaultRateLimiter();
        }
        return rateLimiter.initQuota(criteria);
    }

    private static long getExpireDurationMs(Rule rule) {
        return getMaxSeconds(rule) * 1000 + RateLimitConstants.EXPIRE_FACTOR_MS;
    }

    private static long getMaxSeconds(Rule rule) {
        long maxSeconds = 0;
        for (Amount amount : rule.getAmountsList()) {
            long seconds = amount.getValidDuration().getSeconds();
            if (maxSeconds == 0 || seconds > maxSeconds) {
                maxSeconds = seconds;
            }
        }
        return maxSeconds;
    }

    private String buildQuotaUniqueKey(String ruleRevision) {
        StringBuilder builder = new StringBuilder();
        builder.append(ruleRevision).append(RateLimitConstants.DEFAULT_NAMES_SEPARATOR);
        builder.append(svcKey.getService()).append(RateLimitConstants.DEFAULT_NAMES_SEPARATOR);
        builder.append(svcKey.getNamespace());
        if (StringUtils.isNotBlank(labels)) {
            builder.append(RateLimitConstants.DEFAULT_NAMES_SEPARATOR);
            builder.append(labels);
        }
        return builder.toString();
    }

    public void init() {
        synchronized (initLock) {
            if (!status.compareAndSet(WindowStatus.CREATED.ordinal(), WindowStatus.INITIALIZING.ordinal())) {
                //确保初始化一次
                return;
            }
            if (configMode == RateLimitConstants.CONFIG_QUOTA_LOCAL_MODE) {
                //本地限流,则直接可用
                status.set(WindowStatus.INITIALIZED.ordinal());
                return;
            }
            //加入轮询队列,走异步调度
            windowSet.getRateLimitExtension().submitSyncTask(new RemoteSyncTask(this));
        }
    }

    public void unInit() {
        synchronized (initLock) {
            if (status.get() == WindowStatus.DELETED.ordinal()) {
                return;
            }
            status.set(WindowStatus.DELETED.ordinal());
            //从轮询队列中剔除
            windowSet.getRateLimitExtension().stopSyncTask(uniqueKey);
        }
    }

    public QuotaResult allocateQuota(int count) {
        long curTimeMs = System.currentTimeMillis();
        lastAccessTimeMs.set(curTimeMs);
        return allocatingBucket.allocateQuota(curTimeMs, count);
    }

    /**
     * 窗口已经过期
     *
     * @return boolean
     */
    public boolean isExpired() {
        long curTimeMs = System.currentTimeMillis();
        boolean expired = curTimeMs - lastAccessTimeMs.get() > expireDurationMs;
        if (expired) {
            LOG.info("[RateLimit]window has expired, expireDurationMs {}, uniqueKey {}", expireDurationMs, uniqueKey);
        }
        return expired;
    }

    /**
     * 获取当前窗口的状态
     *
     * @return 窗口状态
     */
    public WindowStatus getStatus() {
        return WindowStatus.class.getEnumConstants()[this.status.get()];
    }

    public void setStatus(int value) {
        this.status.set(value);
    }

    public RateLimitWindowSet getWindowSet() {
        return windowSet;
    }

    public QuotaBucket getAllocatingBucket() {
        return allocatingBucket;
    }

    public ServiceKey getRemoteCluster() {
        return remoteCluster;
    }

    public ServiceInstances getRemoteAddresses() {
        return remoteAddresses;
    }

    public ServiceKey getSvcKey() {
        return svcKey;
    }

    public String getLabels() {
        return labels;
    }

    public Rule getRule() {
        return rule;
    }

    public RateLimitConfig getRateLimitConfig() {
        return rateLimitConfig;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy