Add basic cluster concurrency limiting impl in token server module
Signed-off-by: yunfeiyanggzq <yunfeiyang@buaa.edu.cn>
This commit is contained in:
parent
473cc84262
commit
6314caab80
|
|
@ -24,6 +24,9 @@ public final class ClusterConstants {
|
||||||
public static final int MSG_TYPE_PING = 0;
|
public static final int MSG_TYPE_PING = 0;
|
||||||
public static final int MSG_TYPE_FLOW = 1;
|
public static final int MSG_TYPE_FLOW = 1;
|
||||||
public static final int MSG_TYPE_PARAM_FLOW = 2;
|
public static final int MSG_TYPE_PARAM_FLOW = 2;
|
||||||
|
public static final int MSG_TYPE_CONCURRENT_FLOW_ACQUIRE = 3;
|
||||||
|
public static final int MSG_TYPE_CONCURRENT_FLOW_RELEASE = 4;
|
||||||
|
|
||||||
|
|
||||||
public static final int RESPONSE_STATUS_BAD = -1;
|
public static final int RESPONSE_STATUS_BAD = -1;
|
||||||
public static final int RESPONSE_STATUS_OK = 0;
|
public static final int RESPONSE_STATUS_OK = 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
|
||||||
|
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
*/
|
||||||
|
final public class ConcurrentClusterFlowChecker {
|
||||||
|
|
||||||
|
public static double calcGlobalThreshold(FlowRule rule) {
|
||||||
|
double count = rule.getCount();
|
||||||
|
switch (rule.getClusterConfig().getThresholdType()) {
|
||||||
|
case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
|
||||||
|
return count;
|
||||||
|
case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
|
||||||
|
default:
|
||||||
|
int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());
|
||||||
|
return count * connectedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TokenResult acquireConcurrentToken(/*@Valid*/ String clientAddress, FlowRule rule, int acquireCount) {
|
||||||
|
long flowId = rule.getClusterConfig().getFlowId();
|
||||||
|
AtomicInteger nowCalls = CurrentConcurrencyManager.get(flowId);
|
||||||
|
if (nowCalls == null) {
|
||||||
|
RecordLog.warn("[ConcurrentClusterFlowChecker] Fail to get nowCalls by flowId<{}>", flowId);
|
||||||
|
return new TokenResult(TokenResultStatus.FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check before enter the lock to improve the efficiency
|
||||||
|
if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) {
|
||||||
|
ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount);
|
||||||
|
return new TokenResult(TokenResultStatus.BLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure the atomicity of operations
|
||||||
|
// lock different nowCalls to improve the efficiency
|
||||||
|
synchronized (nowCalls) {
|
||||||
|
// check again whether the request can pass.
|
||||||
|
if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) {
|
||||||
|
ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount);
|
||||||
|
return new TokenResult(TokenResultStatus.BLOCKED);
|
||||||
|
} else {
|
||||||
|
nowCalls.getAndAdd(acquireCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClusterServerStatLogUtil.log("concurrent|pass|" + flowId, acquireCount);
|
||||||
|
TokenCacheNode node = TokenCacheNode.generateTokenCacheNode(rule, acquireCount, clientAddress);
|
||||||
|
TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node);
|
||||||
|
TokenResult tokenResult = new TokenResult(TokenResultStatus.OK);
|
||||||
|
tokenResult.setTokenId(node.getTokenId());
|
||||||
|
return tokenResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TokenResult releaseConcurrentToken(/*@Valid*/ long tokenId) {
|
||||||
|
TokenCacheNode node = TokenCacheNodeManager.getTokenCacheNode(tokenId);
|
||||||
|
if (node == null) {
|
||||||
|
RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released", tokenId);
|
||||||
|
return new TokenResult(TokenResultStatus.ALREADY_RELEASE);
|
||||||
|
}
|
||||||
|
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId());
|
||||||
|
if (rule == null) {
|
||||||
|
RecordLog.info("[ConcurrentClusterFlowChecker] Fail to get rule by flowId<{}>", node.getFlowId());
|
||||||
|
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
|
||||||
|
}
|
||||||
|
if (TokenCacheNodeManager.removeTokenCacheNode(tokenId) == null) {
|
||||||
|
RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released for flowId<{}>", tokenId, node.getFlowId());
|
||||||
|
return new TokenResult(TokenResultStatus.ALREADY_RELEASE);
|
||||||
|
}
|
||||||
|
int acquireCount = node.getAcquireCount();
|
||||||
|
AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId());
|
||||||
|
nowCalls.getAndAdd(-1 * acquireCount);
|
||||||
|
ClusterServerStatLogUtil.log("concurrent|release|" + rule.getClusterConfig().getFlowId(), acquireCount);
|
||||||
|
return new TokenResult(TokenResultStatus.RELEASE_OK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,16 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.cluster.flow;
|
package com.alibaba.csp.sentinel.cluster.flow;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
|
||||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
|
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
|
||||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation for cluster {@link TokenService}.
|
* Default implementation for cluster {@link TokenService}.
|
||||||
*
|
*
|
||||||
|
|
@ -61,10 +61,35 @@ public class DefaultTokenService implements TokenService {
|
||||||
return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
|
return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) {
|
||||||
|
if (notValidRequest(clientAddress, ruleId, acquireCount)) {
|
||||||
|
return badRequest();
|
||||||
|
}
|
||||||
|
// The rule should be valid.
|
||||||
|
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
|
||||||
|
if (rule == null) {
|
||||||
|
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
|
||||||
|
}
|
||||||
|
return ConcurrentClusterFlowChecker.acquireConcurrentToken(clientAddress, rule, acquireCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseConcurrentToken(Long tokenId) {
|
||||||
|
if (tokenId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ConcurrentClusterFlowChecker.releaseConcurrentToken(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean notValidRequest(Long id, int count) {
|
private boolean notValidRequest(Long id, int count) {
|
||||||
return id == null || id <= 0 || count <= 0;
|
return id == null || id <= 0 || count <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean notValidRequest(String address, Long id, int count) {
|
||||||
|
return address == null || "".equals(address) || id == null || id <= 0 || count <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
private TokenResult badRequest() {
|
private TokenResult badRequest() {
|
||||||
return new TokenResult(TokenResultStatus.BAD_REQUEST);
|
return new TokenResult(TokenResultStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.cluster.flow.rule;
|
package com.alibaba.csp.sentinel.cluster.flow.rule;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
|
||||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
||||||
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
||||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
|
||||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||||
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
|
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
|
||||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||||
|
|
@ -41,6 +34,9 @@ import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
import com.alibaba.csp.sentinel.util.function.Function;
|
import com.alibaba.csp.sentinel.util.function.Function;
|
||||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager for cluster flow rules.
|
* Manager for cluster flow rules.
|
||||||
*
|
*
|
||||||
|
|
@ -54,12 +50,12 @@ public final class ClusterFlowRuleManager {
|
||||||
* for a specific namespace to do rule management manually.
|
* for a specific namespace to do rule management manually.
|
||||||
*/
|
*/
|
||||||
public static final Function<String, SentinelProperty<List<FlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
|
public static final Function<String, SentinelProperty<List<FlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
|
||||||
new Function<String, SentinelProperty<List<FlowRule>>>() {
|
new Function<String, SentinelProperty<List<FlowRule>>>() {
|
||||||
@Override
|
@Override
|
||||||
public SentinelProperty<List<FlowRule>> apply(String namespace) {
|
public SentinelProperty<List<FlowRule>> apply(String namespace) {
|
||||||
return new DynamicSentinelProperty<>();
|
return new DynamicSentinelProperty<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (flowId, clusterRule)
|
* (flowId, clusterRule)
|
||||||
|
|
@ -87,7 +83,7 @@ public final class ClusterFlowRuleManager {
|
||||||
* Cluster flow rule property supplier for a specific namespace.
|
* Cluster flow rule property supplier for a specific namespace.
|
||||||
*/
|
*/
|
||||||
private static volatile Function<String, SentinelProperty<List<FlowRule>>> propertySupplier
|
private static volatile Function<String, SentinelProperty<List<FlowRule>>> propertySupplier
|
||||||
= DEFAULT_PROPERTY_SUPPLIER;
|
= DEFAULT_PROPERTY_SUPPLIER;
|
||||||
|
|
||||||
private static final Object UPDATE_LOCK = new Object();
|
private static final Object UPDATE_LOCK = new Object();
|
||||||
|
|
||||||
|
|
@ -118,18 +114,18 @@ public final class ClusterFlowRuleManager {
|
||||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||||
if (propertySupplier == null) {
|
if (propertySupplier == null) {
|
||||||
RecordLog.warn(
|
RecordLog.warn(
|
||||||
"[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property");
|
"[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SentinelProperty<List<FlowRule>> property = propertySupplier.apply(namespace);
|
SentinelProperty<List<FlowRule>> property = propertySupplier.apply(namespace);
|
||||||
if (property == null) {
|
if (property == null) {
|
||||||
RecordLog.warn(
|
RecordLog.warn(
|
||||||
"[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring");
|
"[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (UPDATE_LOCK) {
|
synchronized (UPDATE_LOCK) {
|
||||||
RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager"
|
RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager"
|
||||||
+ " for namespace <{}>", namespace);
|
+ " for namespace <{}>", namespace);
|
||||||
registerPropertyInternal(namespace, property);
|
registerPropertyInternal(namespace, property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +176,7 @@ public final class ClusterFlowRuleManager {
|
||||||
PROPERTY_MAP.remove(namespace);
|
PROPERTY_MAP.remove(namespace);
|
||||||
}
|
}
|
||||||
RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager"
|
RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager"
|
||||||
+ " for namespace <{}>", namespace);
|
+ " for namespace <{}>", namespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +249,7 @@ public final class ClusterFlowRuleManager {
|
||||||
* Load flow rules for a specific namespace. The former rules of the namespace will be replaced.
|
* Load flow rules for a specific namespace. The former rules of the namespace will be replaced.
|
||||||
*
|
*
|
||||||
* @param namespace a valid namespace
|
* @param namespace a valid namespace
|
||||||
* @param rules rule list
|
* @param rules rule list
|
||||||
*/
|
*/
|
||||||
public static void loadRules(String namespace, List<FlowRule> rules) {
|
public static void loadRules(String namespace, List<FlowRule> rules) {
|
||||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||||
|
|
@ -278,6 +274,9 @@ public final class ClusterFlowRuleManager {
|
||||||
for (Long flowId : flowIdSet) {
|
for (Long flowId : flowIdSet) {
|
||||||
FLOW_RULES.remove(flowId);
|
FLOW_RULES.remove(flowId);
|
||||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||||
|
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||||
|
CurrentConcurrencyManager.remove(flowId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
flowIdSet.clear();
|
flowIdSet.clear();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -293,6 +292,9 @@ public final class ClusterFlowRuleManager {
|
||||||
FLOW_RULES.remove(flowId);
|
FLOW_RULES.remove(flowId);
|
||||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||||
ClusterMetricStatistics.removeMetric(flowId);
|
ClusterMetricStatistics.removeMetric(flowId);
|
||||||
|
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||||
|
CurrentConcurrencyManager.remove(flowId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
oldIdSet.clear();
|
oldIdSet.clear();
|
||||||
|
|
@ -335,7 +337,7 @@ public final class ClusterFlowRuleManager {
|
||||||
}
|
}
|
||||||
if (!FlowRuleUtil.isValidRule(rule)) {
|
if (!FlowRuleUtil.isValidRule(rule)) {
|
||||||
RecordLog.warn(
|
RecordLog.warn(
|
||||||
"[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
|
"[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (StringUtil.isBlank(rule.getLimitApp())) {
|
if (StringUtil.isBlank(rule.getLimitApp())) {
|
||||||
|
|
@ -351,10 +353,13 @@ public final class ClusterFlowRuleManager {
|
||||||
ruleMap.put(flowId, rule);
|
ruleMap.put(flowId, rule);
|
||||||
FLOW_NAMESPACE_MAP.put(flowId, namespace);
|
FLOW_NAMESPACE_MAP.put(flowId, namespace);
|
||||||
flowIdSet.add(flowId);
|
flowIdSet.add(flowId);
|
||||||
|
if (!CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||||
|
CurrentConcurrencyManager.put(flowId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare cluster metric from valid flow ID.
|
// Prepare cluster metric from valid flow ID.
|
||||||
ClusterMetricStatistics.putMetricIfAbsent(flowId,
|
ClusterMetricStatistics.putMetricIfAbsent(flowId,
|
||||||
new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
|
new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup unused cluster metrics.
|
// Cleanup unused cluster metrics.
|
||||||
|
|
@ -381,16 +386,17 @@ public final class ClusterFlowRuleManager {
|
||||||
public synchronized void configUpdate(List<FlowRule> conf) {
|
public synchronized void configUpdate(List<FlowRule> conf) {
|
||||||
applyClusterFlowRule(conf, namespace);
|
applyClusterFlowRule(conf, namespace);
|
||||||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}",
|
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}",
|
||||||
namespace, FLOW_RULES);
|
namespace, FLOW_RULES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void configLoad(List<FlowRule> conf) {
|
public synchronized void configLoad(List<FlowRule> conf) {
|
||||||
applyClusterFlowRule(conf, namespace);
|
applyClusterFlowRule(conf, namespace);
|
||||||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}",
|
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}",
|
||||||
namespace, FLOW_RULES);
|
namespace, FLOW_RULES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClusterFlowRuleManager() {}
|
private ClusterFlowRuleManager() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.ConcurrentClusterFlowChecker;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
|
||||||
|
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
*/
|
||||||
|
public class ClusterConcurrentCheckerLogListener implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
collectInformation();
|
||||||
|
} catch (Exception e) {
|
||||||
|
RecordLog.warn("[ClusterConcurrentCheckerLogListener] Failed to record concurrent flow control regularly", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectInformation() {
|
||||||
|
Set<Long> keySet = CurrentConcurrencyManager.getConcurrencyMapKeySet();
|
||||||
|
for (long flowId : keySet) {
|
||||||
|
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId);
|
||||||
|
if (rule == null || CurrentConcurrencyManager.get(flowId).get() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
double concurrencyLevel = ConcurrentClusterFlowChecker.calcGlobalThreshold(rule);
|
||||||
|
String resource = rule.getResource();
|
||||||
|
ClusterServerStatLogUtil.log(String.format("concurrent|resource:%s|flowId:%dl|concurrencyLevel:%fl|currentConcurrency", resource, flowId,concurrencyLevel),CurrentConcurrencyManager.get(flowId).get());
|
||||||
|
}
|
||||||
|
if (TokenCacheNodeManager.getSize() != 0){
|
||||||
|
ClusterServerStatLogUtil.log("flow|totalTokenSize", TokenCacheNodeManager.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use a ConcurrentHashMap<long, AtomicInteger> type structure to store nowCalls corresponding to
|
||||||
|
* rules, where the key is flowId and the value is nowCalls. Because nowCalls may be accessed and
|
||||||
|
* modified by multiple threads, we consider to design it as an AtomicInteger class . Each newly
|
||||||
|
* created rule will add a nowCalls object to this map. If the concurrency corresponding to a rule changes,
|
||||||
|
* we will update the corresponding nowCalls in real time. Each request to obtain a token will increase the nowCalls;
|
||||||
|
* and the request to release the token will reduce the nowCalls.
|
||||||
|
*
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
*/
|
||||||
|
public final class CurrentConcurrencyManager {
|
||||||
|
/**
|
||||||
|
* use ConcurrentHashMap to store the nowCalls of rules.
|
||||||
|
*/
|
||||||
|
private static final ConcurrentHashMap<Long, AtomicInteger> NOW_CALLS_MAP = new ConcurrentHashMap<Long, AtomicInteger>();
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||||
|
private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
|
||||||
|
new NamedThreadFactory("sentinel-cluster-concurrency-record-task", true));
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClusterConcurrentCheckerLogListener logTask = new ClusterConcurrentCheckerLogListener();
|
||||||
|
SCHEDULER.scheduleAtFixedRate(logTask, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add current concurrency.
|
||||||
|
*/
|
||||||
|
public static void addConcurrency(Long flowId, Integer acquireCount) {
|
||||||
|
|
||||||
|
AtomicInteger nowCalls = NOW_CALLS_MAP.get(flowId);
|
||||||
|
if (nowCalls == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nowCalls.getAndAdd(acquireCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the current concurrency.
|
||||||
|
*/
|
||||||
|
public static AtomicInteger get(Long flowId) {
|
||||||
|
return NOW_CALLS_MAP.get(flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete the current concurrency.
|
||||||
|
*/
|
||||||
|
public static void remove(Long flowId) {
|
||||||
|
NOW_CALLS_MAP.remove(flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* put the current concurrency.
|
||||||
|
*/
|
||||||
|
public static void put(Long flowId, Integer nowCalls) {
|
||||||
|
NOW_CALLS_MAP.put(flowId, new AtomicInteger(nowCalls));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check flow id.
|
||||||
|
*/
|
||||||
|
public static boolean containsFlowId(Long flowId) {
|
||||||
|
return NOW_CALLS_MAP.containsKey(flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get NOW_CALLS_MAP.
|
||||||
|
*/
|
||||||
|
public static Set<Long> getConcurrencyMapKeySet() {
|
||||||
|
return NOW_CALLS_MAP.keySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use TokenCacheNodeManager to store the tokenId, whose the underlying storage structure
|
||||||
|
* is ConcurrentLinkedHashMap, Its storage node is TokenCacheNode. In order to operate the nowCalls value when
|
||||||
|
* the expired tokenId is deleted regularly, we need to store the flowId in TokenCacheNode.
|
||||||
|
*
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
*/
|
||||||
|
public class TokenCacheNode {
|
||||||
|
/**
|
||||||
|
* the TokenId of the token
|
||||||
|
*/
|
||||||
|
private Long tokenId;
|
||||||
|
/**
|
||||||
|
* the client goes offline detection time
|
||||||
|
*/
|
||||||
|
private Long clientTimeout;
|
||||||
|
/**
|
||||||
|
* the resource called over time detection time
|
||||||
|
*/
|
||||||
|
private Long resourceTimeout;
|
||||||
|
/**
|
||||||
|
* the flow rule id corresponding to the token
|
||||||
|
*/
|
||||||
|
private Long flowId;
|
||||||
|
/**
|
||||||
|
* the number this token occupied
|
||||||
|
*/
|
||||||
|
private int acquireCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the address of the client holds the token.
|
||||||
|
*/
|
||||||
|
private String clientAddress;
|
||||||
|
|
||||||
|
public TokenCacheNode() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TokenCacheNode generateTokenCacheNode(FlowRule rule, int acquireCount, String clientAddress) {
|
||||||
|
TokenCacheNode node = new TokenCacheNode();
|
||||||
|
// getMostSignificantBits() returns the most significant 64 bits of this UUID's 128 bit value.
|
||||||
|
// The probability of collision is extremely low.
|
||||||
|
node.setTokenId(UUID.randomUUID().getMostSignificantBits());
|
||||||
|
node.setFlowId(rule.getClusterConfig().getFlowId());
|
||||||
|
node.setClientTimeout(rule.getClusterConfig().getClientOfflineTime());
|
||||||
|
node.setResourceTimeout(rule.getClusterConfig().getResourceTimeout());
|
||||||
|
node.setAcquireCount(acquireCount);
|
||||||
|
node.setClientAddress(clientAddress);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getTokenId() {
|
||||||
|
return tokenId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenId(Long tokenId) {
|
||||||
|
this.tokenId = tokenId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getClientTimeout() {
|
||||||
|
return clientTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientTimeout(Long clientTimeout) {
|
||||||
|
this.clientTimeout = clientTimeout + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getResourceTimeout() {
|
||||||
|
return this.resourceTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceTimeout(Long resourceTimeout) {
|
||||||
|
this.resourceTimeout = resourceTimeout + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFlowId() {
|
||||||
|
return flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlowId(Long flowId) {
|
||||||
|
this.flowId = flowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAcquireCount() {
|
||||||
|
return acquireCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAcquireCount(int acquireCount) {
|
||||||
|
this.acquireCount = acquireCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientAddress() {
|
||||||
|
return clientAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientAddress(String clientAddress) {
|
||||||
|
this.clientAddress = clientAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TokenCacheNode{" +
|
||||||
|
"tokenId=" + tokenId +
|
||||||
|
", clientTimeout=" + clientTimeout +
|
||||||
|
", resourceTimeout=" + resourceTimeout +
|
||||||
|
", flowId=" + flowId +
|
||||||
|
", acquireCount=" + acquireCount +
|
||||||
|
", clientAddress='" + clientAddress + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire.RegularExpireStrategy;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
||||||
|
import com.googlecode.concurrentlinkedhashmap.Weighers;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
*/
|
||||||
|
public class TokenCacheNodeManager {
|
||||||
|
private static ConcurrentLinkedHashMap<Long, TokenCacheNode> TOKEN_CACHE_NODE_MAP;
|
||||||
|
|
||||||
|
|
||||||
|
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
|
||||||
|
private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
prepare(DEFAULT_CONCURRENCY_LEVEL, DEFAULT_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void prepare(int concurrencyLevel, int maximumWeightedCapacity) {
|
||||||
|
AssertUtil.isTrue(concurrencyLevel > 0, "concurrencyLevel must be positive");
|
||||||
|
AssertUtil.isTrue(maximumWeightedCapacity > 0, "maximumWeightedCapacity must be positive");
|
||||||
|
|
||||||
|
TOKEN_CACHE_NODE_MAP = new ConcurrentLinkedHashMap.Builder<Long, TokenCacheNode>()
|
||||||
|
.concurrencyLevel(concurrencyLevel)
|
||||||
|
.maximumWeightedCapacity(maximumWeightedCapacity)
|
||||||
|
.weigher(Weighers.singleton())
|
||||||
|
.build();
|
||||||
|
// Start the task of regularly clearing expired keys
|
||||||
|
RegularExpireStrategy strategy = new RegularExpireStrategy(TOKEN_CACHE_NODE_MAP);
|
||||||
|
strategy.startClearTaskRegularly();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static TokenCacheNode getTokenCacheNode(long tokenId) {
|
||||||
|
//use getQuietly to prevent disorder
|
||||||
|
return TOKEN_CACHE_NODE_MAP.getQuietly(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putTokenCacheNode(long tokenId, TokenCacheNode cacheNode) {
|
||||||
|
TOKEN_CACHE_NODE_MAP.put(tokenId, cacheNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isContainsTokenId(long tokenId) {
|
||||||
|
return TOKEN_CACHE_NODE_MAP.containsKey(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TokenCacheNode removeTokenCacheNode(long tokenId) {
|
||||||
|
return TOKEN_CACHE_NODE_MAP.remove(tokenId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getSize() {
|
||||||
|
return TOKEN_CACHE_NODE_MAP.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Long> getCacheKeySet() {
|
||||||
|
return TOKEN_CACHE_NODE_MAP.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validToken(TokenCacheNode cacheNode) {
|
||||||
|
return cacheNode.getTokenId() != null && cacheNode.getFlowId() != null && cacheNode.getClientTimeout() >= 0 && cacheNode.getResourceTimeout() >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yunfeiyagnggzq
|
||||||
|
*/
|
||||||
|
public interface ExpireStrategy {
|
||||||
|
/**
|
||||||
|
* clean expired token regularly.
|
||||||
|
*/
|
||||||
|
void startClearTaskRegularly();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* 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.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode;
|
||||||
|
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||||
|
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||||
|
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||||
|
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||||
|
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to consider the situation that the token client goes offline
|
||||||
|
* or the resource call times out. It can be detected by sourceTimeout
|
||||||
|
* and clientTimeout. The resource calls timeout detection is triggered
|
||||||
|
* on the token client. If the resource is called over time, the token
|
||||||
|
* client will request the token server to release token or refresh the
|
||||||
|
* token. The client offline detection is triggered on the token server.
|
||||||
|
* If the offline detection time is exceeded, token server will trigger
|
||||||
|
* the detection token client’s status. If the token client is offline,
|
||||||
|
* token server will delete the corresponding tokenId. If it is not offline,
|
||||||
|
* token server will continue to save it.
|
||||||
|
*
|
||||||
|
* @author yunfeiyanggzq
|
||||||
|
**/
|
||||||
|
public class RegularExpireStrategy implements ExpireStrategy {
|
||||||
|
/**
|
||||||
|
* The max number of token deleted each time,
|
||||||
|
* the number of expired key-value pairs deleted each time does not exceed this number
|
||||||
|
*/
|
||||||
|
private long executeCount = 1000;
|
||||||
|
/**
|
||||||
|
* Length of time for task execution
|
||||||
|
*/
|
||||||
|
private long executeDuration = 800;
|
||||||
|
/**
|
||||||
|
* Frequency of task execution
|
||||||
|
*/
|
||||||
|
private long executeRate = 1000;
|
||||||
|
/**
|
||||||
|
* the local cache of tokenId
|
||||||
|
*/
|
||||||
|
private ConcurrentLinkedHashMap<Long, TokenCacheNode> localCache;
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||||
|
private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1,
|
||||||
|
new NamedThreadFactory("regular clear expired token thread"));
|
||||||
|
|
||||||
|
|
||||||
|
public RegularExpireStrategy(ConcurrentLinkedHashMap<Long, TokenCacheNode> localCache) {
|
||||||
|
AssertUtil.isTrue(localCache != null, " local cache can't be null");
|
||||||
|
this.localCache = localCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startClearTaskRegularly() {
|
||||||
|
executor.scheduleAtFixedRate(new ClearExpiredTokenTask(), 0, executeRate, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClearExpiredTokenTask implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
clearToken();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
RecordLog.warn("[RegularExpireStrategy] undefined throwable during clear token: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearToken() {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
List<Long> keyList = new ArrayList<>(localCache.keySet());
|
||||||
|
for (int i = 0; i < executeCount && i < keyList.size(); i++) {
|
||||||
|
// time out execution exit
|
||||||
|
if (System.currentTimeMillis() - start > executeDuration) {
|
||||||
|
RecordLog.info("[RegularExpireStrategy] End the process of expired token detection because of execute time is more than executeDuration:", executeDuration);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Long key = keyList.get(i);
|
||||||
|
TokenCacheNode node = localCache.get(key);
|
||||||
|
if (node == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the token whose client is offline and saved for more than clientTimeout
|
||||||
|
if (!ConnectionManager.isClientOnline(node.getClientAddress()) && node.getClientTimeout() - System.currentTimeMillis() < 0) {
|
||||||
|
removeToken(key, node);
|
||||||
|
RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of client offline for ruleId<{}>", node.getTokenId(), node.getFlowId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we find that token's save time is more than 2 times of the client's call resource timeout time,
|
||||||
|
// the token will be determined to timeout.
|
||||||
|
long resourceTimeout = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()).getClusterConfig().getResourceTimeout();
|
||||||
|
if (System.currentTimeMillis() - node.getResourceTimeout() > resourceTimeout) {
|
||||||
|
removeToken(key, node);
|
||||||
|
RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of resource timeout for ruleId<{}>", node.getTokenId(), node.getFlowId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeToken(long tokenId, TokenCacheNode node) {
|
||||||
|
if (localCache.remove(tokenId) == null) {
|
||||||
|
RecordLog.info("[RegularExpireStrategy] Token<{}> is already released for ruleId<{}>", tokenId, node.getFlowId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId());
|
||||||
|
if (nowCalls == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nowCalls.getAndAdd(node.getAcquireCount() * -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -112,6 +112,9 @@ public final class ConnectionManager {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isClientOnline(String address){
|
||||||
|
return NAMESPACE_MAP.containsKey(address);
|
||||||
|
}
|
||||||
static void clear() {
|
static void clear() {
|
||||||
CONN_MAP.clear();
|
CONN_MAP.clear();
|
||||||
NAMESPACE_MAP.clear();
|
NAMESPACE_MAP.clear();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue