init
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>sentinel-cluster</artifactId>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<version>1.8.3</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>sentinel-cluster-server-default</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-transport-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-cluster-common-default</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-parameter-flow-control</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.csp</groupId>
|
||||
<artifactId>sentinel-datasource-nacos</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.TokenResultStatus;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
|
||||
/**
|
||||
* Flow checker for cluster flow rules.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
final class ClusterFlowChecker {
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean allowProceed(long flowId) {
|
||||
String namespace = ClusterFlowRuleManager.getNamespace(flowId);
|
||||
return GlobalRequestLimiter.tryPass(namespace);
|
||||
}
|
||||
|
||||
static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) {
|
||||
Long id = rule.getClusterConfig().getFlowId();
|
||||
|
||||
if (!allowProceed(id)) {
|
||||
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
|
||||
}
|
||||
|
||||
ClusterMetric metric = ClusterMetricStatistics.getMetric(id);
|
||||
if (metric == null) {
|
||||
return new TokenResult(TokenResultStatus.FAIL);
|
||||
}
|
||||
|
||||
double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
|
||||
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount();
|
||||
double nextRemaining = globalThreshold - latestQps - acquireCount;
|
||||
|
||||
if (nextRemaining >= 0) {
|
||||
// TODO: checking logic and metric operation should be separated.
|
||||
metric.add(ClusterFlowEvent.PASS, acquireCount);
|
||||
metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
|
||||
if (prioritized) {
|
||||
// Add prioritized pass.
|
||||
metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);
|
||||
}
|
||||
// Remaining count is cut down to a smaller integer.
|
||||
return new TokenResult(TokenResultStatus.OK)
|
||||
.setRemaining((int) nextRemaining)
|
||||
.setWaitInMs(0);
|
||||
} else {
|
||||
if (prioritized) {
|
||||
// Try to occupy incoming buckets.
|
||||
double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);
|
||||
if (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) {
|
||||
int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);
|
||||
// waitInMs > 0 indicates pre-occupy incoming buckets successfully.
|
||||
if (waitInMs > 0) {
|
||||
ClusterServerStatLogUtil.log("flow|waiting|" + id);
|
||||
return new TokenResult(TokenResultStatus.SHOULD_WAIT)
|
||||
.setRemaining(0)
|
||||
.setWaitInMs(waitInMs);
|
||||
}
|
||||
// Or else occupy failed, should be blocked.
|
||||
}
|
||||
}
|
||||
// Blocked.
|
||||
metric.add(ClusterFlowEvent.BLOCK, acquireCount);
|
||||
metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1);
|
||||
ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount);
|
||||
ClusterServerStatLogUtil.log("flow|block_request|" + id, 1);
|
||||
if (prioritized) {
|
||||
// Add prioritized block.
|
||||
metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount);
|
||||
ClusterServerStatLogUtil.log("flow|occupied_block|" + id, 1);
|
||||
}
|
||||
|
||||
return blockedResult();
|
||||
}
|
||||
}
|
||||
|
||||
private static TokenResult blockedResult() {
|
||||
return new TokenResult(TokenResultStatus.BLOCKED)
|
||||
.setRemaining(0)
|
||||
.setWaitInMs(0);
|
||||
}
|
||||
|
||||
private ClusterFlowChecker() {}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil;
|
||||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterParamFlowChecker {
|
||||
|
||||
static boolean allowProceed(long flowId) {
|
||||
String namespace = ClusterParamFlowRuleManager.getNamespace(flowId);
|
||||
return GlobalRequestLimiter.tryPass(namespace);
|
||||
}
|
||||
|
||||
static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection<Object> values) {
|
||||
Long id = rule.getClusterConfig().getFlowId();
|
||||
|
||||
if (!allowProceed(id)) {
|
||||
return new TokenResult(TokenResultStatus.TOO_MANY_REQUEST);
|
||||
}
|
||||
|
||||
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id);
|
||||
if (metric == null) {
|
||||
// Unexpected state, return FAIL.
|
||||
return new TokenResult(TokenResultStatus.FAIL);
|
||||
}
|
||||
if (values == null || values.isEmpty()) {
|
||||
// Empty parameter list will always pass.
|
||||
return new TokenResult(TokenResultStatus.OK);
|
||||
}
|
||||
double remaining = -1;
|
||||
boolean hasPassed = true;
|
||||
Object blockObject = null;
|
||||
for (Object value : values) {
|
||||
double latestQps = metric.getAvg(value);
|
||||
double threshold = calcGlobalThreshold(rule, value);
|
||||
double nextRemaining = threshold - latestQps - count;
|
||||
remaining = nextRemaining;
|
||||
if (nextRemaining < 0) {
|
||||
hasPassed = false;
|
||||
blockObject = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPassed) {
|
||||
for (Object value : values) {
|
||||
metric.addValue(value, count);
|
||||
}
|
||||
ClusterServerStatLogUtil.log(String.format("param|pass|%d", id));
|
||||
} else {
|
||||
ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject));
|
||||
}
|
||||
if (values.size() > 1) {
|
||||
// Remaining field is unsupported for multi-values.
|
||||
remaining = -1;
|
||||
}
|
||||
|
||||
return hasPassed ? newPassResponse((int)remaining): newBlockResponse();
|
||||
}
|
||||
|
||||
private static TokenResult newPassResponse(int remaining) {
|
||||
return new TokenResult(TokenResultStatus.OK)
|
||||
.setRemaining(remaining)
|
||||
.setWaitInMs(0);
|
||||
}
|
||||
|
||||
private static TokenResult newBlockResponse() {
|
||||
return new TokenResult(TokenResultStatus.BLOCKED)
|
||||
.setRemaining(0)
|
||||
.setWaitInMs(0);
|
||||
}
|
||||
|
||||
private static double calcGlobalThreshold(ParamFlowRule rule, Object value) {
|
||||
double count = getRawThreshold(rule, value);
|
||||
switch (rule.getClusterConfig().getThresholdType()) {
|
||||
case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
|
||||
return count;
|
||||
case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
|
||||
default:
|
||||
int connectedCount = ClusterParamFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId());
|
||||
return count * connectedCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static double getRawThreshold(ParamFlowRule rule, Object value) {
|
||||
Integer itemCount = rule.retrieveExclusiveItemCount(value);
|
||||
if (itemCount == null) {
|
||||
return rule.getCount();
|
||||
} else {
|
||||
return itemCount;
|
||||
}
|
||||
}
|
||||
|
||||
private ClusterParamFlowChecker() {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
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.flow.rule.ClusterFlowRuleManager;
|
||||
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.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.spi.Spi;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Default implementation for cluster {@link TokenService}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@Spi(isDefault = true)
|
||||
public class DefaultTokenService implements TokenService {
|
||||
|
||||
@Override
|
||||
public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
|
||||
if (notValidRequest(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 ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
|
||||
if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) {
|
||||
return badRequest();
|
||||
}
|
||||
// The rule should be valid.
|
||||
ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId);
|
||||
if (rule == null) {
|
||||
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
|
||||
}
|
||||
|
||||
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) {
|
||||
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() {
|
||||
return new TokenResult(TokenResultStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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.rule;
|
||||
|
||||
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.server.ServerConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Function;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manager for cluster flow rules.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterFlowRuleManager {
|
||||
|
||||
/**
|
||||
* The default cluster flow rule property supplier that creates a new dynamic property
|
||||
* for a specific namespace to do rule management manually.
|
||||
*/
|
||||
public static final Function<String, SentinelProperty<List<FlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
|
||||
new Function<String, SentinelProperty<List<FlowRule>>>() {
|
||||
@Override
|
||||
public SentinelProperty<List<FlowRule>> apply(String namespace) {
|
||||
return new DynamicSentinelProperty<>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* (flowId, clusterRule)
|
||||
*/
|
||||
private static final Map<Long, FlowRule> FLOW_RULES = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* (namespace, [flowId...])
|
||||
*/
|
||||
private static final Map<String, Set<Long>> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* <p>This map (flowId, namespace) is used for getting connected count
|
||||
* when checking a specific rule in {@code ruleId}:</p>
|
||||
*
|
||||
* <pre>
|
||||
* ruleId -> namespace -> connection group -> connected count
|
||||
* </pre>
|
||||
*/
|
||||
private static final Map<Long, String> FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* (namespace, property-listener wrapper)
|
||||
*/
|
||||
private static final Map<String, NamespaceFlowProperty<FlowRule>> PROPERTY_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Cluster flow rule property supplier for a specific namespace.
|
||||
*/
|
||||
private static volatile Function<String, SentinelProperty<List<FlowRule>>> propertySupplier
|
||||
= DEFAULT_PROPERTY_SUPPLIER;
|
||||
|
||||
private static final Object UPDATE_LOCK = new Object();
|
||||
|
||||
static {
|
||||
initDefaultProperty();
|
||||
}
|
||||
|
||||
private static void initDefaultProperty() {
|
||||
// The server should always support default namespace,
|
||||
// so register a default property for default namespace.
|
||||
SentinelProperty<List<FlowRule>> defaultProperty = new DynamicSentinelProperty<>();
|
||||
String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE;
|
||||
registerPropertyInternal(defaultNamespace, defaultProperty);
|
||||
}
|
||||
|
||||
public static void setPropertySupplier(Function<String, SentinelProperty<List<FlowRule>>> propertySupplier) {
|
||||
AssertUtil.notNull(propertySupplier, "flow rule property supplier cannot be null");
|
||||
ClusterFlowRuleManager.propertySupplier = propertySupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s.
|
||||
* The property is the source of cluster {@link FlowRule}s for a specific namespace.
|
||||
*
|
||||
* @param namespace namespace to register
|
||||
*/
|
||||
public static void register2Property(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
if (propertySupplier == null) {
|
||||
RecordLog.warn(
|
||||
"[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property");
|
||||
return;
|
||||
}
|
||||
SentinelProperty<List<FlowRule>> property = propertySupplier.apply(namespace);
|
||||
if (property == null) {
|
||||
RecordLog.warn(
|
||||
"[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring");
|
||||
return;
|
||||
}
|
||||
synchronized (UPDATE_LOCK) {
|
||||
RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager"
|
||||
+ " for namespace <{}>", namespace);
|
||||
registerPropertyInternal(namespace, property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s if current property for namespace is absent.
|
||||
* The property is the source of cluster {@link FlowRule}s for a specific namespace.
|
||||
*
|
||||
* @param namespace namespace to register
|
||||
*/
|
||||
public static void registerPropertyIfAbsent(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
if (!PROPERTY_MAP.containsKey(namespace)) {
|
||||
synchronized (UPDATE_LOCK) {
|
||||
if (!PROPERTY_MAP.containsKey(namespace)) {
|
||||
register2Property(namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/
|
||||
SentinelProperty<List<FlowRule>> property) {
|
||||
NamespaceFlowProperty<FlowRule> oldProperty = PROPERTY_MAP.get(namespace);
|
||||
if (oldProperty != null) {
|
||||
oldProperty.getProperty().removeListener(oldProperty.getListener());
|
||||
}
|
||||
PropertyListener<List<FlowRule>> listener = new FlowRulePropertyListener(namespace);
|
||||
property.addListener(listener);
|
||||
PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener));
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet == null) {
|
||||
resetNamespaceFlowIdMapFor(namespace);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cluster flow rule property for a specific namespace.
|
||||
*
|
||||
* @param namespace valid namespace
|
||||
*/
|
||||
public static void removeProperty(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
synchronized (UPDATE_LOCK) {
|
||||
NamespaceFlowProperty<FlowRule> property = PROPERTY_MAP.get(namespace);
|
||||
if (property != null) {
|
||||
property.getProperty().removeListener(property.getListener());
|
||||
PROPERTY_MAP.remove(namespace);
|
||||
}
|
||||
RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager"
|
||||
+ " for namespace <{}>", namespace);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removePropertyListeners() {
|
||||
for (NamespaceFlowProperty<FlowRule> property : PROPERTY_MAP.values()) {
|
||||
property.getProperty().removeListener(property.getListener());
|
||||
}
|
||||
}
|
||||
|
||||
private static void restorePropertyListeners() {
|
||||
for (NamespaceFlowProperty<FlowRule> p : PROPERTY_MAP.values()) {
|
||||
p.getProperty().removeListener(p.getListener());
|
||||
p.getProperty().addListener(p.getListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get flow rule by rule ID.
|
||||
*
|
||||
* @param id rule ID
|
||||
* @return flow rule
|
||||
*/
|
||||
public static FlowRule getFlowRuleById(Long id) {
|
||||
if (!ClusterRuleUtil.validId(id)) {
|
||||
return null;
|
||||
}
|
||||
return FLOW_RULES.get(id);
|
||||
}
|
||||
|
||||
public static Set<Long> getFlowIdSet(String namespace) {
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
Set<Long> set = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (set == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return new HashSet<>(set);
|
||||
}
|
||||
|
||||
public static List<FlowRule> getAllFlowRules() {
|
||||
return new ArrayList<>(FLOW_RULES.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cluster flow rules within a specific namespace.
|
||||
*
|
||||
* @param namespace valid namespace
|
||||
* @return cluster flow rules within the provided namespace
|
||||
*/
|
||||
public static List<FlowRule> getFlowRules(String namespace) {
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<FlowRule> rules = new ArrayList<>();
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet == null || flowIdSet.isEmpty()) {
|
||||
return rules;
|
||||
}
|
||||
for (Long flowId : flowIdSet) {
|
||||
FlowRule rule = FLOW_RULES.get(flowId);
|
||||
if (rule != null) {
|
||||
rules.add(rule);
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load flow rules for a specific namespace. The former rules of the namespace will be replaced.
|
||||
*
|
||||
* @param namespace a valid namespace
|
||||
* @param rules rule list
|
||||
*/
|
||||
public static void loadRules(String namespace, List<FlowRule> rules) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
NamespaceFlowProperty<FlowRule> property = PROPERTY_MAP.get(namespace);
|
||||
if (property != null) {
|
||||
property.getProperty().updateValue(rules);
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) {
|
||||
NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all rules of the provided namespace and reset map.
|
||||
*
|
||||
* @param namespace valid namespace
|
||||
*/
|
||||
private static void clearAndResetRulesFor(/*@Valid*/ String namespace) {
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet != null && !flowIdSet.isEmpty()) {
|
||||
for (Long flowId : flowIdSet) {
|
||||
FLOW_RULES.remove(flowId);
|
||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||
CurrentConcurrencyManager.remove(flowId);
|
||||
}
|
||||
}
|
||||
flowIdSet.clear();
|
||||
} else {
|
||||
resetNamespaceFlowIdMapFor(namespace);
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate<Long> predicate) {
|
||||
Set<Long> oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (oldIdSet != null && !oldIdSet.isEmpty()) {
|
||||
for (Long flowId : oldIdSet) {
|
||||
if (predicate.test(flowId)) {
|
||||
FLOW_RULES.remove(flowId);
|
||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||
ClusterMetricStatistics.removeMetric(flowId);
|
||||
if (CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||
CurrentConcurrencyManager.remove(flowId);
|
||||
}
|
||||
}
|
||||
}
|
||||
oldIdSet.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connected count for associated namespace of given {@code flowId}.
|
||||
*
|
||||
* @param flowId unique flow ID
|
||||
* @return connected count
|
||||
*/
|
||||
public static int getConnectedCount(long flowId) {
|
||||
if (flowId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
String namespace = FLOW_NAMESPACE_MAP.get(flowId);
|
||||
if (namespace == null) {
|
||||
return 0;
|
||||
}
|
||||
return ConnectionManager.getConnectedCount(namespace);
|
||||
}
|
||||
|
||||
public static String getNamespace(long flowId) {
|
||||
return FLOW_NAMESPACE_MAP.get(flowId);
|
||||
}
|
||||
|
||||
private static void applyClusterFlowRule(List<FlowRule> list, /*@Valid*/ String namespace) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
clearAndResetRulesFor(namespace);
|
||||
return;
|
||||
}
|
||||
final ConcurrentHashMap<Long, FlowRule> ruleMap = new ConcurrentHashMap<>();
|
||||
|
||||
Set<Long> flowIdSet = new HashSet<>();
|
||||
|
||||
for (FlowRule rule : list) {
|
||||
if (!rule.isClusterMode()) {
|
||||
continue;
|
||||
}
|
||||
if (!FlowRuleUtil.isValidRule(rule)) {
|
||||
RecordLog.warn(
|
||||
"[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
|
||||
continue;
|
||||
}
|
||||
if (StringUtil.isBlank(rule.getLimitApp())) {
|
||||
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
|
||||
}
|
||||
|
||||
// Flow id should not be null after filtered.
|
||||
ClusterFlowConfig clusterConfig = rule.getClusterConfig();
|
||||
Long flowId = clusterConfig.getFlowId();
|
||||
if (flowId == null) {
|
||||
continue;
|
||||
}
|
||||
ruleMap.put(flowId, rule);
|
||||
FLOW_NAMESPACE_MAP.put(flowId, namespace);
|
||||
flowIdSet.add(flowId);
|
||||
if (!CurrentConcurrencyManager.containsFlowId(flowId)) {
|
||||
CurrentConcurrencyManager.put(flowId, 0);
|
||||
}
|
||||
|
||||
// Prepare cluster metric from valid flow ID.
|
||||
ClusterMetricStatistics.putMetricIfAbsent(flowId,
|
||||
new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
|
||||
}
|
||||
|
||||
// Cleanup unused cluster metrics.
|
||||
clearAndResetRulesConditional(namespace, new Predicate<Long>() {
|
||||
@Override
|
||||
public boolean test(Long flowId) {
|
||||
return !ruleMap.containsKey(flowId);
|
||||
}
|
||||
});
|
||||
|
||||
FLOW_RULES.putAll(ruleMap);
|
||||
NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet);
|
||||
}
|
||||
|
||||
private static final class FlowRulePropertyListener implements PropertyListener<List<FlowRule>> {
|
||||
|
||||
private final String namespace;
|
||||
|
||||
public FlowRulePropertyListener(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void configUpdate(List<FlowRule> conf) {
|
||||
applyClusterFlowRule(conf, namespace);
|
||||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}",
|
||||
namespace, FLOW_RULES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void configLoad(List<FlowRule> conf) {
|
||||
applyClusterFlowRule(conf, namespace);
|
||||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}",
|
||||
namespace, FLOW_RULES);
|
||||
}
|
||||
}
|
||||
|
||||
private ClusterFlowRuleManager() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* 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.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.ClusterParamMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.csp.sentinel.util.function.Function;
|
||||
import com.alibaba.csp.sentinel.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Manager for cluster parameter flow rules.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterParamFlowRuleManager {
|
||||
|
||||
/**
|
||||
* The default cluster parameter flow rule property supplier that creates a new
|
||||
* dynamic property for a specific namespace to manually do rule management.
|
||||
*/
|
||||
public static final Function<String, SentinelProperty<List<ParamFlowRule>>> DEFAULT_PROPERTY_SUPPLIER =
|
||||
new Function<String, SentinelProperty<List<ParamFlowRule>>>() {
|
||||
@Override
|
||||
public SentinelProperty<List<ParamFlowRule>> apply(String namespace) {
|
||||
return new DynamicSentinelProperty<>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* (id, clusterParamRule)
|
||||
*/
|
||||
private static final Map<Long, ParamFlowRule> PARAM_RULES = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* (namespace, [flowId...])
|
||||
*/
|
||||
private static final Map<String, Set<Long>> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* (flowId, namespace)
|
||||
*/
|
||||
private static final Map<Long, String> FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* (namespace, property-listener wrapper)
|
||||
*/
|
||||
private static final Map<String, NamespaceFlowProperty<ParamFlowRule>> PROPERTY_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Cluster parameter flow rule property supplier for a specific namespace.
|
||||
*/
|
||||
private static volatile Function<String, SentinelProperty<List<ParamFlowRule>>> propertySupplier
|
||||
= DEFAULT_PROPERTY_SUPPLIER;
|
||||
|
||||
private static final Object UPDATE_LOCK = new Object();
|
||||
|
||||
static {
|
||||
initDefaultProperty();
|
||||
}
|
||||
|
||||
private static void initDefaultProperty() {
|
||||
SentinelProperty<List<ParamFlowRule>> defaultProperty = new DynamicSentinelProperty<>();
|
||||
String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE;
|
||||
registerPropertyInternal(defaultNamespace, defaultProperty);
|
||||
}
|
||||
|
||||
public static void setPropertySupplier(
|
||||
Function<String, SentinelProperty<List<ParamFlowRule>>> propertySupplier) {
|
||||
ClusterParamFlowRuleManager.propertySupplier = propertySupplier;
|
||||
}
|
||||
|
||||
public static String getNamespace(long flowId) {
|
||||
return FLOW_NAMESPACE_MAP.get(flowId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the {@link SentinelProperty} for cluster {@link ParamFlowRule}s.
|
||||
* The property is the source of cluster {@link ParamFlowRule}s for a specific namespace.
|
||||
*
|
||||
* @param namespace namespace to register
|
||||
*/
|
||||
public static void register2Property(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
if (propertySupplier == null) {
|
||||
RecordLog.warn(
|
||||
"[ClusterParamFlowRuleManager] Cluster param rule property supplier is absent, cannot register "
|
||||
+ "property");
|
||||
return;
|
||||
}
|
||||
SentinelProperty<List<ParamFlowRule>> property = propertySupplier.apply(namespace);
|
||||
if (property == null) {
|
||||
RecordLog.warn(
|
||||
"[ClusterParamFlowRuleManager] Wrong created property from cluster param rule property supplier, "
|
||||
+ "ignoring");
|
||||
return;
|
||||
}
|
||||
synchronized (UPDATE_LOCK) {
|
||||
RecordLog.info("[ClusterParamFlowRuleManager] Registering new property to cluster param rule manager"
|
||||
+ " for namespace <{}>", namespace);
|
||||
registerPropertyInternal(namespace, property);
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerPropertyIfAbsent(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
if (!PROPERTY_MAP.containsKey(namespace)) {
|
||||
synchronized (UPDATE_LOCK) {
|
||||
if (!PROPERTY_MAP.containsKey(namespace)) {
|
||||
register2Property(namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/
|
||||
SentinelProperty<List<ParamFlowRule>> property) {
|
||||
NamespaceFlowProperty<ParamFlowRule> oldProperty = PROPERTY_MAP.get(namespace);
|
||||
if (oldProperty != null) {
|
||||
oldProperty.getProperty().removeListener(oldProperty.getListener());
|
||||
}
|
||||
PropertyListener<List<ParamFlowRule>> listener = new ParamRulePropertyListener(namespace);
|
||||
property.addListener(listener);
|
||||
PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener));
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet == null) {
|
||||
resetNamespaceFlowIdMapFor(namespace);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeProperty(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
synchronized (UPDATE_LOCK) {
|
||||
NamespaceFlowProperty<ParamFlowRule> property = PROPERTY_MAP.get(namespace);
|
||||
if (property != null) {
|
||||
property.getProperty().removeListener(property.getListener());
|
||||
PROPERTY_MAP.remove(namespace);
|
||||
}
|
||||
RecordLog.info("[ClusterParamFlowRuleManager] Removing property from cluster flow rule manager"
|
||||
+ " for namespace <{}>", namespace);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removePropertyListeners() {
|
||||
for (NamespaceFlowProperty<ParamFlowRule> property : PROPERTY_MAP.values()) {
|
||||
property.getProperty().removeListener(property.getListener());
|
||||
}
|
||||
}
|
||||
|
||||
private static void restorePropertyListeners() {
|
||||
for (NamespaceFlowProperty<ParamFlowRule> p : PROPERTY_MAP.values()) {
|
||||
p.getProperty().removeListener(p.getListener());
|
||||
p.getProperty().addListener(p.getListener());
|
||||
}
|
||||
}
|
||||
|
||||
private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) {
|
||||
NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>());
|
||||
}
|
||||
|
||||
private static void clearAndResetRulesFor(/*@Valid*/ String namespace) {
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet != null && !flowIdSet.isEmpty()) {
|
||||
for (Long flowId : flowIdSet) {
|
||||
PARAM_RULES.remove(flowId);
|
||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||
}
|
||||
flowIdSet.clear();
|
||||
} else {
|
||||
resetNamespaceFlowIdMapFor(namespace);
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate<Long> predicate) {
|
||||
Set<Long> oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (oldIdSet != null && !oldIdSet.isEmpty()) {
|
||||
for (Long flowId : oldIdSet) {
|
||||
if (predicate.test(flowId)) {
|
||||
PARAM_RULES.remove(flowId);
|
||||
FLOW_NAMESPACE_MAP.remove(flowId);
|
||||
ClusterParamMetricStatistics.removeMetric(flowId);
|
||||
}
|
||||
}
|
||||
oldIdSet.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static ParamFlowRule getParamRuleById(Long id) {
|
||||
if (!ClusterRuleUtil.validId(id)) {
|
||||
return null;
|
||||
}
|
||||
return PARAM_RULES.get(id);
|
||||
}
|
||||
|
||||
public static Set<Long> getFlowIdSet(String namespace) {
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
Set<Long> set = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (set == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return new HashSet<>(set);
|
||||
}
|
||||
|
||||
public static List<ParamFlowRule> getAllParamRules() {
|
||||
return new ArrayList<>(PARAM_RULES.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cluster parameter flow rules within a specific namespace.
|
||||
*
|
||||
* @param namespace a valid namespace
|
||||
* @return cluster parameter flow rules within the provided namespace
|
||||
*/
|
||||
public static List<ParamFlowRule> getParamRules(String namespace) {
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<ParamFlowRule> rules = new ArrayList<>();
|
||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace);
|
||||
if (flowIdSet == null || flowIdSet.isEmpty()) {
|
||||
return rules;
|
||||
}
|
||||
for (Long flowId : flowIdSet) {
|
||||
ParamFlowRule rule = PARAM_RULES.get(flowId);
|
||||
if (rule != null) {
|
||||
rules.add(rule);
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load parameter flow rules for a specific namespace. The former rules of the namespace will be replaced.
|
||||
*
|
||||
* @param namespace a valid namespace
|
||||
* @param rules rule list
|
||||
*/
|
||||
public static void loadRules(String namespace, List<ParamFlowRule> rules) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
NamespaceFlowProperty<ParamFlowRule> property = PROPERTY_MAP.get(namespace);
|
||||
if (property != null) {
|
||||
property.getProperty().updateValue(rules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connected count for associated namespace of given {@code flowId}.
|
||||
*
|
||||
* @param flowId existing rule ID
|
||||
* @return connected count
|
||||
*/
|
||||
public static int getConnectedCount(long flowId) {
|
||||
if (flowId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
String namespace = FLOW_NAMESPACE_MAP.get(flowId);
|
||||
if (namespace == null) {
|
||||
return 0;
|
||||
}
|
||||
return ConnectionManager.getConnectedCount(namespace);
|
||||
}
|
||||
|
||||
private static class ParamRulePropertyListener implements PropertyListener<List<ParamFlowRule>> {
|
||||
|
||||
private final String namespace;
|
||||
|
||||
public ParamRulePropertyListener(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configLoad(List<ParamFlowRule> conf) {
|
||||
applyClusterParamRules(conf, namespace);
|
||||
RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{}>: {}",
|
||||
namespace, PARAM_RULES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configUpdate(List<ParamFlowRule> conf) {
|
||||
applyClusterParamRules(conf, namespace);
|
||||
RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{}>: {}",
|
||||
namespace, PARAM_RULES);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyClusterParamRules(List<ParamFlowRule> list, /*@Valid*/ String namespace) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
clearAndResetRulesFor(namespace);
|
||||
return;
|
||||
}
|
||||
final ConcurrentHashMap<Long, ParamFlowRule> ruleMap = new ConcurrentHashMap<>();
|
||||
|
||||
Set<Long> flowIdSet = new HashSet<>();
|
||||
|
||||
for (ParamFlowRule rule : list) {
|
||||
if (!rule.isClusterMode()) {
|
||||
continue;
|
||||
}
|
||||
if (!ParamFlowRuleUtil.isValidRule(rule)) {
|
||||
RecordLog.warn(
|
||||
"[ClusterParamFlowRuleManager] Ignoring invalid param flow rule when loading new flow rules: "
|
||||
+ rule);
|
||||
continue;
|
||||
}
|
||||
if (StringUtil.isBlank(rule.getLimitApp())) {
|
||||
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
|
||||
}
|
||||
|
||||
ParamFlowRuleUtil.fillExceptionFlowItems(rule);
|
||||
|
||||
ParamFlowClusterConfig clusterConfig = rule.getClusterConfig();
|
||||
// Flow id should not be null after filtered.
|
||||
Long flowId = clusterConfig.getFlowId();
|
||||
if (flowId == null) {
|
||||
continue;
|
||||
}
|
||||
ruleMap.put(flowId, rule);
|
||||
FLOW_NAMESPACE_MAP.put(flowId, namespace);
|
||||
flowIdSet.add(flowId);
|
||||
|
||||
// Prepare cluster parameter metric from valid rule ID.
|
||||
ClusterParamMetricStatistics.putMetricIfAbsent(flowId,
|
||||
new ClusterParamMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()));
|
||||
}
|
||||
|
||||
// Cleanup unused cluster parameter metrics.
|
||||
clearAndResetRulesConditional(namespace, new Predicate<Long>() {
|
||||
@Override
|
||||
public boolean test(Long flowId) {
|
||||
return !ruleMap.containsKey(flowId);
|
||||
}
|
||||
});
|
||||
|
||||
PARAM_RULES.putAll(ruleMap);
|
||||
NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet);
|
||||
}
|
||||
|
||||
private ClusterParamFlowRuleManager() {}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.rule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
|
||||
/**
|
||||
* A property wrapper for list of rules of a given namespace.
|
||||
* This is useful for auto-management of the property and listener.
|
||||
*
|
||||
* @param <T> type of the rule
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
class NamespaceFlowProperty<T> {
|
||||
|
||||
private final String namespace;
|
||||
private final SentinelProperty<List<T>> property;
|
||||
private final PropertyListener<List<T>> listener;
|
||||
|
||||
public NamespaceFlowProperty(String namespace,
|
||||
SentinelProperty<List<T>> property,
|
||||
PropertyListener<List<T>> listener) {
|
||||
this.namespace = namespace;
|
||||
this.property = property;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public SentinelProperty<List<T>> getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public PropertyListener<List<T>> getListener() {
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public class ClusterMetricNode {
|
||||
|
||||
private long timestamp;
|
||||
|
||||
private String resourceName;
|
||||
private long flowId;
|
||||
|
||||
private double passQps;
|
||||
private double blockQps;
|
||||
private long rt;
|
||||
|
||||
private Map<Object, Double> topParams;
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setResourceName(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getFlowId() {
|
||||
return flowId;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setFlowId(long flowId) {
|
||||
this.flowId = flowId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getPassQps() {
|
||||
return passQps;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setPassQps(double passQps) {
|
||||
this.passQps = passQps;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getBlockQps() {
|
||||
return blockQps;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setBlockQps(double blockQps) {
|
||||
this.blockQps = blockQps;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getRt() {
|
||||
return rt;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setRt(long rt) {
|
||||
this.rt = rt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<Object, Double> getTopParams() {
|
||||
return topParams;
|
||||
}
|
||||
|
||||
public ClusterMetricNode setTopParams(Map<Object, Double> topParams) {
|
||||
this.topParams = topParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClusterMetricNode{" +
|
||||
"timestamp=" + timestamp +
|
||||
", resourceName='" + resourceName + '\'' +
|
||||
", flowId=" + flowId +
|
||||
", passQps=" + passQps +
|
||||
", blockQps=" + blockQps +
|
||||
", rt=" + rt +
|
||||
", topParams=" + topParams +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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.statistic.data.ClusterFlowEvent;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public class ClusterMetricNodeGenerator {
|
||||
|
||||
public static Map<String, List<ClusterMetricNode>> generateCurrentNodeMap(String namespace) {
|
||||
Map<String, List<ClusterMetricNode>> map = new HashMap<>();
|
||||
Set<Long> flowIds = ClusterFlowRuleManager.getFlowIdSet(namespace);
|
||||
Set<Long> paramFlowIds = ClusterParamFlowRuleManager.getFlowIdSet(namespace);
|
||||
for (Long id : flowIds) {
|
||||
ClusterMetricNode node = flowToMetricNode(id);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
putToMap(map, node);
|
||||
}
|
||||
for (Long id : paramFlowIds) {
|
||||
ClusterMetricNode node = paramToMetricNode(id);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
putToMap(map, node);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void putToMap(Map<String, List<ClusterMetricNode>> map, ClusterMetricNode node) {
|
||||
List<ClusterMetricNode> nodeList = map.get(node.getResourceName());
|
||||
if (nodeList == null) {
|
||||
nodeList = new ArrayList<>();
|
||||
map.put(node.getResourceName(), nodeList);
|
||||
}
|
||||
nodeList.add(node);
|
||||
}
|
||||
|
||||
public static ClusterMetricNode flowToMetricNode(long flowId) {
|
||||
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId);
|
||||
if (rule == null) {
|
||||
return null;
|
||||
}
|
||||
ClusterMetric metric = ClusterMetricStatistics.getMetric(flowId);
|
||||
if (metric == null) {
|
||||
return new ClusterMetricNode().setFlowId(flowId)
|
||||
.setResourceName(rule.getResource());
|
||||
}
|
||||
return new ClusterMetricNode()
|
||||
.setFlowId(flowId)
|
||||
.setResourceName(rule.getResource())
|
||||
.setBlockQps(metric.getAvg(ClusterFlowEvent.BLOCK))
|
||||
.setPassQps(metric.getAvg(ClusterFlowEvent.PASS))
|
||||
.setTimestamp(TimeUtil.currentTimeMillis());
|
||||
}
|
||||
|
||||
public static ClusterMetricNode paramToMetricNode(long flowId) {
|
||||
ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(flowId);
|
||||
if (rule == null) {
|
||||
return null;
|
||||
}
|
||||
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(flowId);
|
||||
if (metric == null) {
|
||||
return new ClusterMetricNode().setFlowId(flowId)
|
||||
.setResourceName(rule.getResource())
|
||||
.setTimestamp(TimeUtil.currentTimeMillis())
|
||||
.setTopParams(new HashMap<Object, Double>(0));
|
||||
}
|
||||
return new ClusterMetricNode()
|
||||
.setFlowId(flowId)
|
||||
.setResourceName(rule.getResource())
|
||||
.setTimestamp(TimeUtil.currentTimeMillis())
|
||||
.setTopParams(metric.getTopValues(5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterMetricStatistics {
|
||||
|
||||
private static final Map<Long, ClusterMetric> METRIC_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void clear() {
|
||||
METRIC_MAP.clear();
|
||||
}
|
||||
|
||||
public static void putMetric(long id, ClusterMetric metric) {
|
||||
AssertUtil.notNull(metric, "Cluster metric cannot be null");
|
||||
METRIC_MAP.put(id, metric);
|
||||
}
|
||||
|
||||
public static boolean putMetricIfAbsent(long id, ClusterMetric metric) {
|
||||
AssertUtil.notNull(metric, "Cluster metric cannot be null");
|
||||
if (METRIC_MAP.containsKey(id)) {
|
||||
return false;
|
||||
}
|
||||
METRIC_MAP.put(id, metric);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void removeMetric(long id) {
|
||||
METRIC_MAP.remove(id);
|
||||
}
|
||||
|
||||
public static ClusterMetric getMetric(long id) {
|
||||
return METRIC_MAP.get(id);
|
||||
}
|
||||
|
||||
public static void resetFlowMetrics() {
|
||||
Set<Long> keySet = METRIC_MAP.keySet();
|
||||
for (Long id : keySet) {
|
||||
METRIC_MAP.put(id, new ClusterMetric(ClusterServerConfigManager.getSampleCount(),
|
||||
ClusterServerConfigManager.getIntervalMs()));
|
||||
}
|
||||
}
|
||||
|
||||
private ClusterMetricStatistics() {}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterParamMetricStatistics {
|
||||
|
||||
private static final Map<Long, ClusterParamMetric> METRIC_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void clear() {
|
||||
METRIC_MAP.clear();
|
||||
}
|
||||
|
||||
public static void putMetric(long id, ClusterParamMetric metric) {
|
||||
AssertUtil.notNull(metric, "metric cannot be null");
|
||||
METRIC_MAP.put(id, metric);
|
||||
}
|
||||
|
||||
public static boolean putMetricIfAbsent(long id, ClusterParamMetric metric) {
|
||||
AssertUtil.notNull(metric, "metric cannot be null");
|
||||
if (METRIC_MAP.containsKey(id)) {
|
||||
return false;
|
||||
}
|
||||
METRIC_MAP.put(id, metric);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void removeMetric(long id) {
|
||||
METRIC_MAP.remove(id);
|
||||
}
|
||||
|
||||
public static ClusterParamMetric getMetric(long id) {
|
||||
return METRIC_MAP.get(id);
|
||||
}
|
||||
|
||||
public static void resetFlowMetrics() {
|
||||
Set<Long> keySet = METRIC_MAP.keySet();
|
||||
for (Long id : keySet) {
|
||||
METRIC_MAP.put(id, new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(),
|
||||
ClusterServerConfigManager.getIntervalMs()));
|
||||
}
|
||||
}
|
||||
|
||||
private ClusterParamMetricStatistics() {}
|
||||
}
|
||||
@@ -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", true));
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.data;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public enum ClusterFlowEvent {
|
||||
|
||||
/**
|
||||
* Normal pass.
|
||||
*/
|
||||
PASS,
|
||||
/**
|
||||
* Normal block.
|
||||
*/
|
||||
BLOCK,
|
||||
/**
|
||||
* Token request (from client) passed.
|
||||
*/
|
||||
PASS_REQUEST,
|
||||
/**
|
||||
* Token request (from client) blocked.
|
||||
*/
|
||||
BLOCK_REQUEST,
|
||||
/**
|
||||
* Pass (pre-occupy incoming buckets).
|
||||
*/
|
||||
OCCUPIED_PASS,
|
||||
/**
|
||||
* Block (pre-occupy incoming buckets failed).
|
||||
*/
|
||||
OCCUPIED_BLOCK,
|
||||
/**
|
||||
* Waiting due to flow shaping or for next bucket tick.
|
||||
*/
|
||||
WAITING
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.data;
|
||||
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterMetricBucket {
|
||||
|
||||
private final LongAdder[] counters;
|
||||
|
||||
public ClusterMetricBucket() {
|
||||
ClusterFlowEvent[] events = ClusterFlowEvent.values();
|
||||
this.counters = new LongAdder[events.length];
|
||||
for (ClusterFlowEvent event : events) {
|
||||
counters[event.ordinal()] = new LongAdder();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
for (ClusterFlowEvent event : ClusterFlowEvent.values()) {
|
||||
counters[event.ordinal()].reset();
|
||||
}
|
||||
}
|
||||
|
||||
public long get(ClusterFlowEvent event) {
|
||||
return counters[event.ordinal()].sum();
|
||||
}
|
||||
|
||||
public ClusterMetricBucket add(ClusterFlowEvent event, long count) {
|
||||
counters[event.ordinal()].add(count);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.limit;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public final class GlobalRequestLimiter {
|
||||
|
||||
private static final Map<String, RequestLimiter> GLOBAL_QPS_LIMITER_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
public static void initIfAbsent(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
if (!GLOBAL_QPS_LIMITER_MAP.containsKey(namespace)) {
|
||||
GLOBAL_QPS_LIMITER_MAP.put(namespace, new RequestLimiter(ClusterServerConfigManager.getMaxAllowedQps(namespace)));
|
||||
}
|
||||
}
|
||||
|
||||
public static RequestLimiter getRequestLimiter(String namespace) {
|
||||
if (namespace == null) {
|
||||
return null;
|
||||
}
|
||||
return GLOBAL_QPS_LIMITER_MAP.get(namespace);
|
||||
}
|
||||
|
||||
public static boolean tryPass(String namespace) {
|
||||
if (namespace == null) {
|
||||
return false;
|
||||
}
|
||||
RequestLimiter limiter = GLOBAL_QPS_LIMITER_MAP.get(namespace);
|
||||
if (limiter == null) {
|
||||
return true;
|
||||
}
|
||||
return limiter.tryPass();
|
||||
}
|
||||
|
||||
public static double getCurrentQps(String namespace) {
|
||||
RequestLimiter limiter = getRequestLimiter(namespace);
|
||||
if (limiter == null) {
|
||||
return 0;
|
||||
}
|
||||
return limiter.getQps();
|
||||
}
|
||||
|
||||
public static double getMaxAllowedQps(String namespace) {
|
||||
RequestLimiter limiter = getRequestLimiter(namespace);
|
||||
if (limiter == null) {
|
||||
return 0;
|
||||
}
|
||||
return limiter.getQpsAllowed();
|
||||
}
|
||||
|
||||
public static void applyMaxQpsChange(double maxAllowedQps) {
|
||||
AssertUtil.isTrue(maxAllowedQps >= 0, "max allowed QPS should > 0");
|
||||
for (RequestLimiter limiter : GLOBAL_QPS_LIMITER_MAP.values()) {
|
||||
if (limiter != null) {
|
||||
limiter.setQpsAllowed(maxAllowedQps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalRequestLimiter() {}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.limit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.UnaryLeapArray;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public class RequestLimiter {
|
||||
|
||||
private double qpsAllowed;
|
||||
|
||||
private final LeapArray<LongAdder> data;
|
||||
|
||||
public RequestLimiter(double qpsAllowed) {
|
||||
this(new UnaryLeapArray(10, 1000), qpsAllowed);
|
||||
}
|
||||
|
||||
RequestLimiter(LeapArray<LongAdder> data, double qpsAllowed) {
|
||||
AssertUtil.isTrue(qpsAllowed >= 0, "max allowed QPS should > 0");
|
||||
this.data = data;
|
||||
this.qpsAllowed = qpsAllowed;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
data.currentWindow().value().increment();
|
||||
}
|
||||
|
||||
public void add(int x) {
|
||||
data.currentWindow().value().add(x);
|
||||
}
|
||||
|
||||
public long getSum() {
|
||||
data.currentWindow();
|
||||
long success = 0;
|
||||
|
||||
List<LongAdder> list = data.values();
|
||||
for (LongAdder window : list) {
|
||||
success += window.sum();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public double getQps() {
|
||||
return getSum() / data.getIntervalInSecond();
|
||||
}
|
||||
|
||||
public double getQpsAllowed() {
|
||||
return qpsAllowed;
|
||||
}
|
||||
|
||||
public boolean canPass() {
|
||||
return getQps() + 1 <= qpsAllowed;
|
||||
}
|
||||
|
||||
public RequestLimiter setQpsAllowed(double qpsAllowed) {
|
||||
this.qpsAllowed = qpsAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean tryPass() {
|
||||
if (canPass()) {
|
||||
add(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterMetric {
|
||||
|
||||
private final ClusterMetricLeapArray metric;
|
||||
|
||||
public ClusterMetric(int sampleCount, int intervalInMs) {
|
||||
AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive");
|
||||
AssertUtil.isTrue(intervalInMs > 0, "interval should be positive");
|
||||
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
|
||||
this.metric = new ClusterMetricLeapArray(sampleCount, intervalInMs);
|
||||
}
|
||||
|
||||
public void add(ClusterFlowEvent event, long count) {
|
||||
metric.currentWindow().value().add(event, count);
|
||||
}
|
||||
|
||||
public long getCurrentCount(ClusterFlowEvent event) {
|
||||
return metric.currentWindow().value().get(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total sum for provided event in {@code intervalInSec}.
|
||||
*
|
||||
* @param event event to calculate
|
||||
* @return total sum for event
|
||||
*/
|
||||
public long getSum(ClusterFlowEvent event) {
|
||||
metric.currentWindow();
|
||||
long sum = 0;
|
||||
|
||||
List<ClusterMetricBucket> buckets = metric.values();
|
||||
for (ClusterMetricBucket bucket : buckets) {
|
||||
sum += bucket.get(event);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average count for provided event per second.
|
||||
*
|
||||
* @param event event to calculate
|
||||
* @return average count per second for event
|
||||
*/
|
||||
public double getAvg(ClusterFlowEvent event) {
|
||||
return getSum(event) / metric.getIntervalInSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pre-occupy upcoming buckets.
|
||||
*
|
||||
* @return time to wait for next bucket (in ms); 0 if cannot occupy next buckets
|
||||
*/
|
||||
public int tryOccupyNext(ClusterFlowEvent event, int acquireCount, double threshold) {
|
||||
double latestQps = getAvg(ClusterFlowEvent.PASS);
|
||||
if (!canOccupy(event, acquireCount, latestQps, threshold)) {
|
||||
return 0;
|
||||
}
|
||||
metric.addOccupyPass(acquireCount);
|
||||
add(ClusterFlowEvent.WAITING, acquireCount);
|
||||
return 1000 / metric.getSampleCount();
|
||||
}
|
||||
|
||||
private boolean canOccupy(ClusterFlowEvent event, int acquireCount, double latestQps, double threshold) {
|
||||
long headPass = metric.getFirstCountOfWindow(event);
|
||||
long occupiedCount = metric.getOccupiedCount(event);
|
||||
// bucket to occupy (= incoming bucket)
|
||||
// ↓
|
||||
// | head bucket | | | | current bucket |
|
||||
// +-------------+----+----+----+----------- ----+
|
||||
// (headPass)
|
||||
return latestQps + (acquireCount + occupiedCount) - headPass <= threshold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterMetricLeapArray extends LeapArray<ClusterMetricBucket> {
|
||||
|
||||
private final LongAdder[] occupyCounter;
|
||||
private boolean hasOccupied = false;
|
||||
|
||||
public ClusterMetricLeapArray(int sampleCount, int intervalInMs) {
|
||||
super(sampleCount, intervalInMs);
|
||||
ClusterFlowEvent[] events = ClusterFlowEvent.values();
|
||||
this.occupyCounter = new LongAdder[events.length];
|
||||
for (ClusterFlowEvent event : events) {
|
||||
occupyCounter[event.ordinal()] = new LongAdder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMetricBucket newEmptyBucket(long timeMillis) {
|
||||
return new ClusterMetricBucket();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WindowWrap<ClusterMetricBucket> resetWindowTo(WindowWrap<ClusterMetricBucket> w, long startTime) {
|
||||
w.resetTo(startTime);
|
||||
w.value().reset();
|
||||
transferOccupyToBucket(w.value());
|
||||
return w;
|
||||
}
|
||||
|
||||
private void transferOccupyToBucket(/*@Valid*/ ClusterMetricBucket bucket) {
|
||||
if (hasOccupied) {
|
||||
transferOccupiedCount(bucket, ClusterFlowEvent.PASS, ClusterFlowEvent.OCCUPIED_PASS);
|
||||
transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS);
|
||||
transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS_REQUEST);
|
||||
hasOccupied = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void transferOccupiedCount(ClusterMetricBucket bucket, ClusterFlowEvent source, ClusterFlowEvent target) {
|
||||
bucket.add(target, occupyCounter[source.ordinal()].sum());
|
||||
}
|
||||
|
||||
private void transferOccupiedThenReset(ClusterMetricBucket bucket, ClusterFlowEvent event) {
|
||||
bucket.add(event, occupyCounter[event.ordinal()].sumThenReset());
|
||||
}
|
||||
|
||||
public void addOccupyPass(int count) {
|
||||
occupyCounter[ClusterFlowEvent.PASS.ordinal()].add(count);
|
||||
occupyCounter[ClusterFlowEvent.PASS_REQUEST.ordinal()].add(1);
|
||||
this.hasOccupied = true;
|
||||
}
|
||||
|
||||
public long getOccupiedCount(ClusterFlowEvent event) {
|
||||
return occupyCounter[event.ordinal()].sum();
|
||||
}
|
||||
|
||||
public long getFirstCountOfWindow(ClusterFlowEvent event) {
|
||||
if (event == null) {
|
||||
return 0;
|
||||
}
|
||||
WindowWrap<ClusterMetricBucket> windowWrap = getValidHead();
|
||||
if (windowWrap == null) {
|
||||
return 0;
|
||||
}
|
||||
return windowWrap.value().get(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterParamMetric {
|
||||
|
||||
public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000;
|
||||
|
||||
private final ClusterParameterLeapArray<LongAdder> metric;
|
||||
|
||||
public ClusterParamMetric(int sampleCount, int intervalInMs) {
|
||||
this(sampleCount, intervalInMs, DEFAULT_CLUSTER_MAX_CAPACITY);
|
||||
}
|
||||
|
||||
public ClusterParamMetric(int sampleCount, int intervalInMs, int maxCapacity) {
|
||||
AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive");
|
||||
AssertUtil.isTrue(intervalInMs > 0, "interval should be positive");
|
||||
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
|
||||
this.metric = new ClusterParameterLeapArray<>(sampleCount, intervalInMs, maxCapacity);
|
||||
}
|
||||
|
||||
public long getSum(Object value) {
|
||||
if (value == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
metric.currentWindow();
|
||||
long sum = 0;
|
||||
|
||||
List<CacheMap<Object, LongAdder>> buckets = metric.values();
|
||||
for (CacheMap<Object, LongAdder> bucket : buckets) {
|
||||
long count = getCount(bucket.get(value));
|
||||
sum += count;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private long getCount(/*@Nullable*/ LongAdder adder) {
|
||||
return adder == null ? 0 : adder.sum();
|
||||
}
|
||||
|
||||
public void addValue(Object value, int count) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
CacheMap<Object, LongAdder> data = metric.currentWindow().value();
|
||||
LongAdder newCounter = new LongAdder();
|
||||
LongAdder currentCounter = data.putIfAbsent(value, newCounter);
|
||||
if (currentCounter != null) {
|
||||
currentCounter.add(count);
|
||||
} else {
|
||||
newCounter.add(count);
|
||||
}
|
||||
}
|
||||
|
||||
public double getAvg(Object value) {
|
||||
return getSum(value) / metric.getIntervalInSecond();
|
||||
}
|
||||
|
||||
public Map<Object, Double> getTopValues(int number) {
|
||||
AssertUtil.isTrue(number > 0, "number must be positive");
|
||||
metric.currentWindow();
|
||||
List<CacheMap<Object, LongAdder>> buckets = metric.values();
|
||||
|
||||
Map<Object, Long> result = new HashMap<>(buckets.size());
|
||||
|
||||
for (CacheMap<Object, LongAdder> b : buckets) {
|
||||
Set<Object> subSet = b.keySet(true);
|
||||
for (Object o : subSet) {
|
||||
Long count = result.get(o);
|
||||
if (count == null) {
|
||||
count = getCount(b.get(o));
|
||||
} else {
|
||||
count += getCount(b.get(o));
|
||||
}
|
||||
result.put(o, count);
|
||||
}
|
||||
}
|
||||
|
||||
// After merge, get the top set one.
|
||||
Set<Entry<Object, Long>> set = result.entrySet();
|
||||
List<Entry<Object, Long>> list = new ArrayList<>(set);
|
||||
Collections.sort(list, new Comparator<Entry<Object, Long>>() {
|
||||
@Override
|
||||
public int compare(Entry<Object, Long> a,
|
||||
Entry<Object, Long> b) {
|
||||
return (int) (b.getValue() == null ? 0 : b.getValue()) - (int) (a.getValue() == null ? 0 : a.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
Map<Object, Double> doubleResult = new HashMap<Object, Double>();
|
||||
|
||||
int size = list.size() > number ? number : list.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
Map.Entry<Object, Long> x = list.get(i);
|
||||
if (x.getValue() == 0) {
|
||||
break;
|
||||
}
|
||||
doubleResult.put(x.getKey(), ((double) x.getValue()) / metric.getIntervalInSecond());
|
||||
}
|
||||
|
||||
return doubleResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @param <C> counter type
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>> {
|
||||
|
||||
private final int maxCapacity;
|
||||
|
||||
public ClusterParameterLeapArray(int sampleCount, int intervalInMs, int maxCapacity) {
|
||||
super(sampleCount, intervalInMs);
|
||||
AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive");
|
||||
this.maxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMap<Object, C> newEmptyBucket(long timeMillis) {
|
||||
return new ConcurrentLinkedHashMapWrapper<>(maxCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WindowWrap<CacheMap<Object, C>> resetWindowTo(WindowWrap<CacheMap<Object, C>> w, long startTime) {
|
||||
w.resetTo(startTime);
|
||||
w.value().clear();
|
||||
return w;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
|
||||
/**
|
||||
* Default embedded token server in Sentinel which wraps the {@link SentinelDefaultTokenServer}
|
||||
* and the {@link TokenService} from SPI provider.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer {
|
||||
|
||||
private final TokenService tokenService = TokenServiceProvider.getService();
|
||||
private final ClusterTokenServer server = new SentinelDefaultTokenServer(true);
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
|
||||
if (tokenService != null) {
|
||||
return tokenService.requestToken(ruleId, acquireCount, prioritized);
|
||||
}
|
||||
return new TokenResult(TokenResultStatus.FAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection<Object> params) {
|
||||
if (tokenService != null) {
|
||||
return tokenService.requestParamToken(ruleId, acquireCount, params);
|
||||
}
|
||||
return new TokenResult(TokenResultStatus.FAIL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenResult requestConcurrentToken(String clientAddress, Long ruleId, int acquireCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseConcurrentToken(Long tokenId) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyRequestDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyResponseEncoder;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.Connection;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool;
|
||||
import com.alibaba.csp.sentinel.cluster.server.handler.TokenServerHandler;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.LengthFieldPrepender;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import io.netty.util.internal.SystemPropertyUtil;
|
||||
|
||||
import static com.alibaba.csp.sentinel.cluster.server.ServerConstants.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class NettyTransportServer implements ClusterTokenServer {
|
||||
|
||||
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
|
||||
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
|
||||
private static final int MAX_RETRY_TIMES = 3;
|
||||
private static final int RETRY_SLEEP_MS = 2000;
|
||||
|
||||
private final int port;
|
||||
|
||||
private NioEventLoopGroup bossGroup;
|
||||
private NioEventLoopGroup workerGroup;
|
||||
|
||||
private final ConnectionPool connectionPool = new ConnectionPool();
|
||||
|
||||
private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF);
|
||||
private final AtomicInteger failedTimes = new AtomicInteger(0);
|
||||
|
||||
public NettyTransportServer(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
this.bossGroup = new NioEventLoopGroup(1);
|
||||
this.workerGroup = new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS);
|
||||
b.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
|
||||
p.addLast(new NettyRequestDecoder());
|
||||
p.addLast(new LengthFieldPrepender(2));
|
||||
p.addLast(new NettyResponseEncoder());
|
||||
p.addLast(new TokenServerHandler(connectionPool));
|
||||
}
|
||||
})
|
||||
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||
.childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
|
||||
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
|
||||
.childOption(ChannelOption.SO_TIMEOUT, 10)
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
|
||||
b.bind(port).addListener(new GenericFutureListener<ChannelFuture>() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (future.cause() != null) {
|
||||
RecordLog.info("[NettyTransportServer] Token server start failed (port=" + port + "), failedTimes: " + failedTimes.get(),
|
||||
future.cause());
|
||||
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF);
|
||||
int failCount = failedTimes.incrementAndGet();
|
||||
if (failCount > MAX_RETRY_TIMES) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(failCount * RETRY_SLEEP_MS);
|
||||
start();
|
||||
} catch (Throwable e) {
|
||||
RecordLog.info("[NettyTransportServer] Failed to start token server when retrying", e);
|
||||
}
|
||||
} else {
|
||||
RecordLog.info("[NettyTransportServer] Token server started success at port {}", port);
|
||||
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
// If still initializing, wait for ready.
|
||||
while (currentState.get() == SERVER_STATUS_STARTING) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) {
|
||||
try {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
connectionPool.shutdownAll();
|
||||
|
||||
failedTimes.set(0);
|
||||
|
||||
RecordLog.info("[NettyTransportServer] Sentinel token server stopped");
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[NettyTransportServer] Failed to stop token server (port=" + port + ")", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshRunningServer() {
|
||||
connectionPool.refreshIdleTask();
|
||||
}
|
||||
|
||||
public void closeConnection(String clientIp, int clientPort) throws Exception {
|
||||
Connection connection = connectionPool.getConnection(clientIp, clientPort);
|
||||
connection.close();
|
||||
}
|
||||
|
||||
public void closeAll() throws Exception {
|
||||
List<Connection> connections = connectionPool.listAllConnection();
|
||||
for (Connection connection : connections) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> listAllClient() {
|
||||
List<String> clients = new ArrayList<String>();
|
||||
List<Connection> connections = connectionPool.listAllConnection();
|
||||
for (Connection conn : connections) {
|
||||
clients.add(conn.getConnectionKey());
|
||||
}
|
||||
return clients;
|
||||
}
|
||||
|
||||
public int getCurrentState() {
|
||||
return currentState.get();
|
||||
}
|
||||
|
||||
public int clientCount() {
|
||||
return connectionPool.count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
|
||||
import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfigObserver;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.init.InitExecutor;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.HostNameUtil;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class SentinelDefaultTokenServer implements ClusterTokenServer {
|
||||
|
||||
private final boolean embedded;
|
||||
|
||||
private ClusterTokenServer server;
|
||||
private int port;
|
||||
private final AtomicBoolean shouldStart = new AtomicBoolean(false);
|
||||
|
||||
static {
|
||||
InitExecutor.doInit();
|
||||
}
|
||||
|
||||
public SentinelDefaultTokenServer() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public SentinelDefaultTokenServer(boolean embedded) {
|
||||
this.embedded = embedded;
|
||||
ClusterServerConfigManager.addTransportConfigChangeObserver(new ServerTransportConfigObserver() {
|
||||
@Override
|
||||
public void onTransportConfigChange(ServerTransportConfig config) {
|
||||
changeServerConfig(config);
|
||||
}
|
||||
});
|
||||
initNewServer();
|
||||
}
|
||||
|
||||
private void initNewServer() {
|
||||
if (server != null) {
|
||||
return;
|
||||
}
|
||||
int port = ClusterServerConfigManager.getPort();
|
||||
if (port > 0) {
|
||||
this.server = new NettyTransportServer(port);
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void changeServerConfig(ServerTransportConfig config) {
|
||||
if (config == null || config.getPort() <= 0) {
|
||||
return;
|
||||
}
|
||||
int newPort = config.getPort();
|
||||
if (newPort == port) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (server != null) {
|
||||
stopServer();
|
||||
}
|
||||
this.server = new NettyTransportServer(newPort);
|
||||
this.port = newPort;
|
||||
startServerIfScheduled();
|
||||
} catch (Exception ex) {
|
||||
RecordLog.warn("[SentinelDefaultTokenServer] Failed to apply modification to token server", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void startServerIfScheduled() throws Exception {
|
||||
if (shouldStart.get()) {
|
||||
if (server != null) {
|
||||
server.start();
|
||||
ClusterStateManager.markToServer();
|
||||
if (embedded) {
|
||||
RecordLog.info("[SentinelDefaultTokenServer] Running in embedded mode");
|
||||
handleEmbeddedStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopServer() throws Exception {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
if (embedded) {
|
||||
handleEmbeddedStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEmbeddedStop() {
|
||||
String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get();
|
||||
if (StringUtil.isNotEmpty(namespace)) {
|
||||
ConnectionManager.removeConnection(namespace, HostNameUtil.getIp());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEmbeddedStart() {
|
||||
String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get();
|
||||
if (StringUtil.isNotEmpty(namespace)) {
|
||||
// Mark server global mode as embedded.
|
||||
ClusterServerConfigManager.setEmbedded(true);
|
||||
if (!ClusterServerConfigManager.getNamespaceSet().contains(namespace)) {
|
||||
Set<String> namespaceSet = new HashSet<>(ClusterServerConfigManager.getNamespaceSet());
|
||||
namespaceSet.add(namespace);
|
||||
ClusterServerConfigManager.loadServerNamespaceSet(namespaceSet);
|
||||
}
|
||||
|
||||
// Register self to connection group.
|
||||
ConnectionManager.addConnection(namespace, HostNameUtil.getIp());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
if (shouldStart.compareAndSet(false, true)) {
|
||||
startServerIfScheduled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
if (shouldStart.compareAndSet(true, false)) {
|
||||
stopServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ServerConstants {
|
||||
|
||||
public static final int SERVER_STATUS_OFF = 0;
|
||||
public static final int SERVER_STATUS_STARTING = 1;
|
||||
public static final int SERVER_STATUS_STARTED = 2;
|
||||
|
||||
public static final String DEFAULT_NAMESPACE = "default";
|
||||
|
||||
private ServerConstants() {}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class TokenServiceProvider {
|
||||
|
||||
private static TokenService service = null;
|
||||
|
||||
static {
|
||||
resolveTokenServiceSpi();
|
||||
}
|
||||
|
||||
public static TokenService getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
private static void resolveTokenServiceSpi() {
|
||||
service = SpiLoader.of(TokenService.class).loadFirstInstanceOrDefault();
|
||||
if (service != null) {
|
||||
RecordLog.info("[TokenServiceProvider] Global token service resolved: "
|
||||
+ service.getClass().getCanonicalName());
|
||||
} else {
|
||||
RecordLog.warn("[TokenServiceProvider] Unable to resolve TokenService: no SPI found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.server.codec;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* <p>Default entity decoder for any {@link ClusterRequest} entity.</p>
|
||||
*
|
||||
* <p>Decode format:</p>
|
||||
* <pre>
|
||||
* +--------+---------+---------+
|
||||
* | xid(4) | type(1) | data... |
|
||||
* +--------+---------+---------+
|
||||
* </pre>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class DefaultRequestEntityDecoder implements RequestEntityDecoder<ByteBuf, ClusterRequest> {
|
||||
|
||||
@Override
|
||||
public ClusterRequest decode(ByteBuf source) {
|
||||
if (source.readableBytes() >= 5) {
|
||||
int xid = source.readInt();
|
||||
int type = source.readByte();
|
||||
|
||||
EntityDecoder<ByteBuf, ?> dataDecoder = RequestDataDecodeRegistry.getDecoder(type);
|
||||
if (dataDecoder == null) {
|
||||
RecordLog.warn("Unknown type of request data decoder: {}", type);
|
||||
return null;
|
||||
}
|
||||
|
||||
Object data;
|
||||
if (source.readableBytes() == 0) {
|
||||
data = null;
|
||||
} else {
|
||||
data = dataDecoder.decode(source);
|
||||
}
|
||||
|
||||
return new ClusterRequest<>(xid, type, data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.server.codec;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
import com.alibaba.csp.sentinel.cluster.response.Response;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class DefaultResponseEntityWriter implements ResponseEntityWriter<ClusterResponse, ByteBuf> {
|
||||
|
||||
@Override
|
||||
public void writeTo(ClusterResponse response, ByteBuf out) {
|
||||
int type = response.getType();
|
||||
EntityWriter<Object, ByteBuf> responseDataWriter = ResponseDataWriterRegistry.getWriter(type);
|
||||
|
||||
if (responseDataWriter == null) {
|
||||
writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out);
|
||||
RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{}>", response.getType());
|
||||
return;
|
||||
}
|
||||
writeHead(response, out);
|
||||
responseDataWriter.writeTo(response.getData(), out);
|
||||
}
|
||||
|
||||
private void writeHead(Response response, ByteBuf out) {
|
||||
out.writeInt(response.getId());
|
||||
out.writeByte(response.getType());
|
||||
out.writeByte(response.getStatus());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.server.codec;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ServerEntityCodecProvider {
|
||||
|
||||
private static RequestEntityDecoder requestEntityDecoder = null;
|
||||
private static ResponseEntityWriter responseEntityWriter = null;
|
||||
|
||||
static {
|
||||
resolveInstance();
|
||||
}
|
||||
|
||||
private static void resolveInstance() {
|
||||
ResponseEntityWriter writer = SpiLoader.of(ResponseEntityWriter.class).loadFirstInstance();
|
||||
if (writer == null) {
|
||||
RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed");
|
||||
} else {
|
||||
responseEntityWriter = writer;
|
||||
RecordLog.info("[ServerEntityCodecProvider] Response entity writer resolved: {}",
|
||||
responseEntityWriter.getClass().getCanonicalName());
|
||||
}
|
||||
RequestEntityDecoder decoder = SpiLoader.of(RequestEntityDecoder.class).loadFirstInstance();
|
||||
if (decoder == null) {
|
||||
RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed");
|
||||
} else {
|
||||
requestEntityDecoder = decoder;
|
||||
RecordLog.info("[ServerEntityCodecProvider] Request entity decoder resolved: {}",
|
||||
requestEntityDecoder.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
public static RequestEntityDecoder getRequestEntityDecoder() {
|
||||
return requestEntityDecoder;
|
||||
}
|
||||
|
||||
public static ResponseEntityWriter getResponseEntityWriter() {
|
||||
return responseEntityWriter;
|
||||
}
|
||||
|
||||
private ServerEntityCodecProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.server.codec.data;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout:
|
||||
* </p>
|
||||
* <pre>
|
||||
* | flow ID (8) | count (4) | priority flag (1) |
|
||||
* </pre>
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class FlowRequestDataDecoder implements EntityDecoder<ByteBuf, FlowRequestData> {
|
||||
|
||||
@Override
|
||||
public FlowRequestData decode(ByteBuf source) {
|
||||
if (source.readableBytes() >= 12) {
|
||||
FlowRequestData requestData = new FlowRequestData()
|
||||
.setFlowId(source.readLong())
|
||||
.setCount(source.readInt());
|
||||
if (source.readableBytes() >= 1) {
|
||||
requestData.setPriority(source.readBoolean());
|
||||
}
|
||||
return requestData;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.server.codec.data;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class FlowResponseDataWriter implements EntityWriter<FlowTokenResponseData, ByteBuf> {
|
||||
|
||||
@Override
|
||||
public void writeTo(FlowTokenResponseData entity, ByteBuf out) {
|
||||
out.writeInt(entity.getRemainingCount());
|
||||
out.writeInt(entity.getWaitInMs());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.server.codec.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ParamFlowRequestDataDecoder implements EntityDecoder<ByteBuf, ParamFlowRequestData> {
|
||||
|
||||
@Override
|
||||
public ParamFlowRequestData decode(ByteBuf source) {
|
||||
if (source.readableBytes() >= 16) {
|
||||
ParamFlowRequestData requestData = new ParamFlowRequestData()
|
||||
.setFlowId(source.readLong())
|
||||
.setCount(source.readInt());
|
||||
|
||||
int amount = source.readInt();
|
||||
if (amount > 0) {
|
||||
List<Object> params = new ArrayList<>(amount);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
decodeParam(source, params);
|
||||
}
|
||||
|
||||
requestData.setParams(params);
|
||||
return requestData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean decodeParam(ByteBuf source, List<Object> params) {
|
||||
byte paramType = source.readByte();
|
||||
|
||||
switch (paramType) {
|
||||
case ClusterConstants.PARAM_TYPE_INTEGER:
|
||||
params.add(source.readInt());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_STRING:
|
||||
int length = source.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
source.readBytes(bytes);
|
||||
// TODO: take care of charset?
|
||||
params.add(new String(bytes));
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_BOOLEAN:
|
||||
params.add(source.readBoolean());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_DOUBLE:
|
||||
params.add(source.readDouble());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_LONG:
|
||||
params.add(source.readLong());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_FLOAT:
|
||||
params.add(source.readFloat());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_BYTE:
|
||||
params.add(source.readByte());
|
||||
return true;
|
||||
case ClusterConstants.PARAM_TYPE_SHORT:
|
||||
params.add(source.readShort());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.server.codec.data;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class PingRequestDataDecoder implements EntityDecoder<ByteBuf, String> {
|
||||
|
||||
@Override
|
||||
public String decode(ByteBuf source) {
|
||||
if (source.readableBytes() >= 4) {
|
||||
int length = source.readInt();
|
||||
if (length > 0 && source.readableBytes() > 0) {
|
||||
byte[] bytes = new byte[length];
|
||||
source.readBytes(bytes);
|
||||
return new String(bytes);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.server.codec.data;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class PingResponseDataWriter implements EntityWriter<Integer, ByteBuf> {
|
||||
|
||||
@Override
|
||||
public void writeTo(Integer entity, ByteBuf target) {
|
||||
if (entity == null || target == null) {
|
||||
return;
|
||||
}
|
||||
target.writeInt(entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.server.codec.netty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.request.Request;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class NettyRequestDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
RequestEntityDecoder<ByteBuf, Request> requestDecoder = ServerEntityCodecProvider.getRequestEntityDecoder();
|
||||
if (requestDecoder == null) {
|
||||
RecordLog.warn("[NettyRequestDecoder] Cannot resolve the global request entity decoder, "
|
||||
+ "dropping the request");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: handle decode error here.
|
||||
Request request = requestDecoder.decode(in);
|
||||
if (request != null) {
|
||||
out.add(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.server.codec.netty;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
import com.alibaba.csp.sentinel.cluster.response.Response;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class NettyResponseEncoder extends MessageToByteEncoder<ClusterResponse> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception {
|
||||
ResponseEntityWriter<ClusterResponse, ByteBuf> responseEntityWriter = ServerEntityCodecProvider.getResponseEntityWriter();
|
||||
if (responseEntityWriter == null) {
|
||||
RecordLog.warn("[NettyResponseEncoder] Cannot resolve the global response entity writer, reply bad status");
|
||||
writeBadStatusHead(response, out);
|
||||
return;
|
||||
}
|
||||
|
||||
responseEntityWriter.writeTo(response, out);
|
||||
}
|
||||
|
||||
|
||||
private void writeBadStatusHead(Response response, ByteBuf out) {
|
||||
out.writeInt(response.getId());
|
||||
out.writeByte(ClusterConstants.RESPONSE_STATUS_BAD);
|
||||
out.writeByte(response.getStatus());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.server.codec.registry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class RequestDataDecodeRegistry {
|
||||
|
||||
private static final Map<Integer, EntityDecoder<ByteBuf, ?>> DECODER_MAP = new HashMap<>();
|
||||
|
||||
public static boolean addDecoder(int type, EntityDecoder<ByteBuf, ?> decoder) {
|
||||
if (DECODER_MAP.containsKey(type)) {
|
||||
return false;
|
||||
}
|
||||
DECODER_MAP.put(type, decoder);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static EntityDecoder<ByteBuf, Object> getDecoder(int type) {
|
||||
return (EntityDecoder<ByteBuf, Object>)DECODER_MAP.get(type);
|
||||
}
|
||||
|
||||
public static boolean removeDecoder(int type) {
|
||||
return DECODER_MAP.remove(type) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.server.codec.registry;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ResponseDataWriterRegistry {
|
||||
|
||||
private static final Map<Integer, EntityWriter<Object, ByteBuf>> WRITER_MAP = new HashMap<>();
|
||||
|
||||
public static <T> boolean addWriter(int type, EntityWriter<T, ByteBuf> writer) {
|
||||
if (WRITER_MAP.containsKey(type)) {
|
||||
return false;
|
||||
}
|
||||
WRITER_MAP.put(type, (EntityWriter<Object, ByteBuf>)writer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static EntityWriter<Object, ByteBuf> getWriter(int type) {
|
||||
return WRITER_MAP.get(type);
|
||||
}
|
||||
|
||||
public static boolean remove(int type) {
|
||||
return WRITER_MAP.remove(type) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/flowRules", desc = "get cluster flow rules")
|
||||
public class FetchClusterFlowRulesCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getAllFlowRules()));
|
||||
} else {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getFlowRules(namespace)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricNodeGenerator;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.1
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/metricList", desc = "get cluster server metrics")
|
||||
public class FetchClusterMetricCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("failed: namespace cannot be empty"));
|
||||
}
|
||||
return CommandResponse.ofSuccess(
|
||||
JSON.toJSONString(ClusterMetricNodeGenerator.generateCurrentNodeMap(namespace))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/paramRules", desc = "get cluster server param flow rules")
|
||||
public class FetchClusterParamFlowRulesCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getAllParamRules()));
|
||||
} else {
|
||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getParamRules(namespace)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/fetchConfig", desc = "get cluster server config")
|
||||
public class FetchClusterServerConfigHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return globalConfigResult();
|
||||
}
|
||||
return namespaceConfigResult(namespace);
|
||||
}
|
||||
|
||||
private CommandResponse<String> namespaceConfigResult(/*@NonEmpty*/ String namespace) {
|
||||
ServerFlowConfig flowConfig = new ServerFlowConfig()
|
||||
.setExceedCount(ClusterServerConfigManager.getExceedCount(namespace))
|
||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio(namespace))
|
||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs(namespace))
|
||||
.setSampleCount(ClusterServerConfigManager.getSampleCount(namespace));
|
||||
JSONObject config = new JSONObject()
|
||||
.fluentPut("flow", flowConfig);
|
||||
return CommandResponse.ofSuccess(config.toJSONString());
|
||||
}
|
||||
|
||||
private CommandResponse<String> globalConfigResult() {
|
||||
ServerTransportConfig transportConfig = new ServerTransportConfig()
|
||||
.setPort(ClusterServerConfigManager.getPort())
|
||||
.setIdleSeconds(ClusterServerConfigManager.getIdleSeconds());
|
||||
ServerFlowConfig flowConfig = new ServerFlowConfig()
|
||||
.setExceedCount(ClusterServerConfigManager.getExceedCount())
|
||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio())
|
||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs())
|
||||
.setSampleCount(ClusterServerConfigManager.getSampleCount());
|
||||
JSONObject config = new JSONObject()
|
||||
.fluentPut("transport", transportConfig)
|
||||
.fluentPut("flow", flowConfig)
|
||||
.fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet());
|
||||
return CommandResponse.ofSuccess(config.toJSONString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionGroup;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.AppNameUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/info", desc = "get cluster server info")
|
||||
public class FetchClusterServerInfoCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
JSONObject info = new JSONObject();
|
||||
JSONArray connectionGroups = new JSONArray();
|
||||
Set<String> namespaceSet = ClusterServerConfigManager.getNamespaceSet();
|
||||
for (String namespace : namespaceSet) {
|
||||
ConnectionGroup group = ConnectionManager.getOrCreateConnectionGroup(namespace);
|
||||
if (group != null) {
|
||||
connectionGroups.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
ServerTransportConfig transportConfig = new ServerTransportConfig()
|
||||
.setPort(ClusterServerConfigManager.getPort())
|
||||
.setIdleSeconds(ClusterServerConfigManager.getIdleSeconds());
|
||||
ServerFlowConfig flowConfig = new ServerFlowConfig()
|
||||
.setExceedCount(ClusterServerConfigManager.getExceedCount())
|
||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio())
|
||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs())
|
||||
.setSampleCount(ClusterServerConfigManager.getSampleCount())
|
||||
.setMaxAllowedQps(ClusterServerConfigManager.getMaxAllowedQps());
|
||||
|
||||
JSONArray requestLimitData = buildRequestLimitData(namespaceSet);
|
||||
|
||||
info.fluentPut("port", ClusterServerConfigManager.getPort())
|
||||
.fluentPut("connection", connectionGroups)
|
||||
.fluentPut("requestLimitData", requestLimitData)
|
||||
.fluentPut("transport", transportConfig)
|
||||
.fluentPut("flow", flowConfig)
|
||||
.fluentPut("namespaceSet", namespaceSet)
|
||||
.fluentPut("embedded", ClusterServerConfigManager.isEmbedded());
|
||||
|
||||
// Since 1.5.0 the appName is carried so that the caller can identify the appName of the token server.
|
||||
info.put("appName", AppNameUtil.getAppName());
|
||||
|
||||
return CommandResponse.ofSuccess(info.toJSONString());
|
||||
}
|
||||
|
||||
private JSONArray buildRequestLimitData(Set<String> namespaceSet) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (String namespace : namespaceSet) {
|
||||
array.add(new JSONObject()
|
||||
.fluentPut("namespace", namespace)
|
||||
.fluentPut("currentQps", GlobalRequestLimiter.getCurrentQps(namespace))
|
||||
.fluentPut("maxAllowedQps", GlobalRequestLimiter.getMaxAllowedQps(namespace))
|
||||
);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/modifyFlowRules", desc = "modify cluster flow rules")
|
||||
public class ModifyClusterFlowRulesCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace"));
|
||||
}
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data"));
|
||||
}
|
||||
try {
|
||||
data = URLDecoder.decode(data, "UTF-8");
|
||||
RecordLog.info("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{}>: {}", namespace, data);
|
||||
|
||||
List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class);
|
||||
ClusterFlowRuleManager.loadRules(namespace, flowRules);
|
||||
|
||||
return CommandResponse.ofSuccess(SUCCESS);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[ModifyClusterFlowRulesCommandHandler] Decode cluster flow rules error", e);
|
||||
return CommandResponse.ofFailure(e, "decode cluster flow rules error");
|
||||
}
|
||||
}
|
||||
|
||||
private static final String SUCCESS = "success";
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/modifyParamRules", desc = "modify cluster param flow rules")
|
||||
public class ModifyClusterParamFlowRulesCommandHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String namespace = request.getParam("namespace");
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace"));
|
||||
}
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data"));
|
||||
}
|
||||
try {
|
||||
data = URLDecoder.decode(data, "UTF-8");
|
||||
RecordLog.info("Receiving cluster param rules for namespace <{}> from command handler: {}", namespace, data);
|
||||
|
||||
List<ParamFlowRule> flowRules = JSONArray.parseArray(data, ParamFlowRule.class);
|
||||
ClusterParamFlowRuleManager.loadRules(namespace, flowRules);
|
||||
|
||||
return CommandResponse.ofSuccess(SUCCESS);
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[ModifyClusterParamFlowRulesCommandHandler] Decode cluster param rules error", e);
|
||||
return CommandResponse.ofFailure(e, "decode cluster param rules error");
|
||||
}
|
||||
}
|
||||
|
||||
private static final String SUCCESS = "success";
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/modifyFlowConfig", desc = "modify cluster server flow config")
|
||||
public class ModifyClusterServerFlowConfigHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data"));
|
||||
}
|
||||
String namespace = request.getParam("namespace");
|
||||
try {
|
||||
data = URLDecoder.decode(data, "utf-8");
|
||||
|
||||
if (StringUtil.isEmpty(namespace)) {
|
||||
RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: {}", data);
|
||||
ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class);
|
||||
if (!ClusterServerConfigManager.isValidFlowConfig(config)) {
|
||||
CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config"));
|
||||
}
|
||||
ClusterServerConfigManager.loadGlobalFlowConfig(config);
|
||||
} else {
|
||||
RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{}>: {}", namespace, data);
|
||||
ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class);
|
||||
if (!ClusterServerConfigManager.isValidFlowConfig(config)) {
|
||||
CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config"));
|
||||
}
|
||||
ClusterServerConfigManager.loadFlowConfig(namespace, config);
|
||||
}
|
||||
|
||||
return CommandResponse.ofSuccess("success");
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[ModifyClusterServerFlowConfigHandler] Decode cluster server flow config error", e);
|
||||
return CommandResponse.ofFailure(e, "decode cluster server flow config error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/modifyTransportConfig", desc = "modify cluster server transport config")
|
||||
public class ModifyClusterServerTransportConfigHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String portValue = request.getParam("port");
|
||||
if (StringUtil.isBlank(portValue)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty port"));
|
||||
}
|
||||
String idleSecondsValue = request.getParam("idleSeconds");
|
||||
if (StringUtil.isBlank(idleSecondsValue)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty idleSeconds"));
|
||||
}
|
||||
try {
|
||||
int port = Integer.valueOf(portValue);
|
||||
int idleSeconds = Integer.valueOf(idleSecondsValue);
|
||||
|
||||
ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig()
|
||||
.setPort(port).setIdleSeconds(idleSeconds));
|
||||
return CommandResponse.ofSuccess("success");
|
||||
} catch (NumberFormatException e) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter"));
|
||||
} catch (Exception ex) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("unexpected error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.server.command.handler;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.command.CommandHandler;
|
||||
import com.alibaba.csp.sentinel.command.CommandRequest;
|
||||
import com.alibaba.csp.sentinel.command.CommandResponse;
|
||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@CommandMapping(name = "cluster/server/modifyNamespaceSet", desc = "modify server namespace set")
|
||||
public class ModifyServerNamespaceSetHandler implements CommandHandler<String> {
|
||||
|
||||
@Override
|
||||
public CommandResponse<String> handle(CommandRequest request) {
|
||||
String data = request.getParam("data");
|
||||
if (StringUtil.isBlank(data)) {
|
||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data"));
|
||||
}
|
||||
try {
|
||||
data = URLDecoder.decode(data, "utf-8");
|
||||
RecordLog.info("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: {}", data);
|
||||
Set<String> set = JSON.parseObject(data, new TypeReference<Set<String>>() {});
|
||||
ClusterServerConfigManager.loadServerNamespaceSet(set);
|
||||
return CommandResponse.ofSuccess("success");
|
||||
} catch (Exception e) {
|
||||
RecordLog.warn("[ModifyServerNamespaceSetHandler] Decode cluster server namespace set error", e);
|
||||
return CommandResponse.ofFailure(e, "decode client cluster config error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* 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.server.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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.ClusterConstants;
|
||||
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.statistic.ClusterMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.limit.GlobalRequestLimiter;
|
||||
import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry;
|
||||
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
|
||||
import com.alibaba.csp.sentinel.property.PropertyListener;
|
||||
import com.alibaba.csp.sentinel.property.SentinelProperty;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterServerConfigManager {
|
||||
|
||||
private static boolean embedded = false;
|
||||
|
||||
/**
|
||||
* Server global transport and scope config.
|
||||
*/
|
||||
private static volatile int port = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT;
|
||||
private static volatile int idleSeconds = ServerTransportConfig.DEFAULT_IDLE_SECONDS;
|
||||
private static volatile Set<String> namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE);
|
||||
|
||||
/**
|
||||
* Server global flow config.
|
||||
*/
|
||||
private static volatile double exceedCount = ServerFlowConfig.DEFAULT_EXCEED_COUNT;
|
||||
private static volatile double maxOccupyRatio = ServerFlowConfig.DEFAULT_MAX_OCCUPY_RATIO;
|
||||
private static volatile int intervalMs = ServerFlowConfig.DEFAULT_INTERVAL_MS;
|
||||
private static volatile int sampleCount = ServerFlowConfig.DEFAULT_SAMPLE_COUNT;
|
||||
private static volatile double maxAllowedQps = ServerFlowConfig.DEFAULT_MAX_ALLOWED_QPS;
|
||||
|
||||
/**
|
||||
* Namespace-specific flow config for token server.
|
||||
* Format: (namespace, config).
|
||||
*/
|
||||
private static final Map<String, ServerFlowConfig> NAMESPACE_CONF = new ConcurrentHashMap<>();
|
||||
|
||||
private static final List<ServerTransportConfigObserver> TRANSPORT_CONFIG_OBSERVERS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Property for cluster server global transport configuration.
|
||||
*/
|
||||
private static SentinelProperty<ServerTransportConfig> transportConfigProperty = new DynamicSentinelProperty<>();
|
||||
/**
|
||||
* Property for cluster server namespace set.
|
||||
*/
|
||||
private static SentinelProperty<Set<String>> namespaceSetProperty = new DynamicSentinelProperty<>();
|
||||
/**
|
||||
* Property for cluster server global flow control configuration.
|
||||
*/
|
||||
private static SentinelProperty<ServerFlowConfig> globalFlowProperty = new DynamicSentinelProperty<>();
|
||||
|
||||
private static final PropertyListener<ServerTransportConfig> TRANSPORT_PROPERTY_LISTENER
|
||||
= new ServerGlobalTransportPropertyListener();
|
||||
private static final PropertyListener<ServerFlowConfig> GLOBAL_FLOW_PROPERTY_LISTENER
|
||||
= new ServerGlobalFlowPropertyListener();
|
||||
private static final PropertyListener<Set<String>> NAMESPACE_SET_PROPERTY_LISTENER
|
||||
= new ServerNamespaceSetPropertyListener();
|
||||
|
||||
static {
|
||||
transportConfigProperty.addListener(TRANSPORT_PROPERTY_LISTENER);
|
||||
globalFlowProperty.addListener(GLOBAL_FLOW_PROPERTY_LISTENER);
|
||||
namespaceSetProperty.addListener(NAMESPACE_SET_PROPERTY_LISTENER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cluster server namespace set dynamic property.
|
||||
*
|
||||
* @param property server namespace set dynamic property
|
||||
*/
|
||||
public static void registerNamespaceSetProperty(SentinelProperty<Set<String>> property) {
|
||||
AssertUtil.notNull(property, "namespace set dynamic property cannot be null");
|
||||
synchronized (NAMESPACE_SET_PROPERTY_LISTENER) {
|
||||
RecordLog.info(
|
||||
"[ClusterServerConfigManager] Registering new namespace set dynamic property to Sentinel server "
|
||||
+ "config manager");
|
||||
namespaceSetProperty.removeListener(NAMESPACE_SET_PROPERTY_LISTENER);
|
||||
property.addListener(NAMESPACE_SET_PROPERTY_LISTENER);
|
||||
namespaceSetProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cluster server transport configuration dynamic property.
|
||||
*
|
||||
* @param property server transport configuration dynamic property
|
||||
*/
|
||||
public static void registerServerTransportProperty(SentinelProperty<ServerTransportConfig> property) {
|
||||
AssertUtil.notNull(property, "cluster server transport config dynamic property cannot be null");
|
||||
synchronized (TRANSPORT_PROPERTY_LISTENER) {
|
||||
RecordLog.info(
|
||||
"[ClusterServerConfigManager] Registering new server transport dynamic property to Sentinel server "
|
||||
+ "config manager");
|
||||
transportConfigProperty.removeListener(TRANSPORT_PROPERTY_LISTENER);
|
||||
property.addListener(TRANSPORT_PROPERTY_LISTENER);
|
||||
transportConfigProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register cluster server global statistic (flow) configuration dynamic property.
|
||||
*
|
||||
* @param property server flow configuration dynamic property
|
||||
*/
|
||||
public static void registerGlobalServerFlowProperty(SentinelProperty<ServerFlowConfig> property) {
|
||||
AssertUtil.notNull(property, "cluster server flow config dynamic property cannot be null");
|
||||
synchronized (GLOBAL_FLOW_PROPERTY_LISTENER) {
|
||||
RecordLog.info(
|
||||
"[ClusterServerConfigManager] Registering new server global flow dynamic property "
|
||||
+ "to Sentinel server config manager");
|
||||
globalFlowProperty.removeListener(GLOBAL_FLOW_PROPERTY_LISTENER);
|
||||
property.addListener(GLOBAL_FLOW_PROPERTY_LISTENER);
|
||||
globalFlowProperty = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load provided server namespace set to property in memory.
|
||||
*
|
||||
* @param namespaceSet valid namespace set
|
||||
*/
|
||||
public static void loadServerNamespaceSet(Set<String> namespaceSet) {
|
||||
namespaceSetProperty.updateValue(namespaceSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load provided server transport configuration to property in memory.
|
||||
*
|
||||
* @param config valid cluster server transport configuration
|
||||
*/
|
||||
public static void loadGlobalTransportConfig(ServerTransportConfig config) {
|
||||
transportConfigProperty.updateValue(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load provided server global statistic (flow) configuration to property in memory.
|
||||
*
|
||||
* @param config valid cluster server flow configuration for global
|
||||
*/
|
||||
public static void loadGlobalFlowConfig(ServerFlowConfig config) {
|
||||
globalFlowProperty.updateValue(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load server flow config for a specific namespace.
|
||||
*
|
||||
* @param namespace a valid namespace
|
||||
* @param config valid flow config for the namespace
|
||||
*/
|
||||
public static void loadFlowConfig(String namespace, ServerFlowConfig config) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
// TODO: Support namespace-scope server flow config.
|
||||
globalFlowProperty.updateValue(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a transport config observer. The observers will be called as soon as
|
||||
* there are some changes in transport config (e.g. token server port).
|
||||
*
|
||||
* @param observer a valid transport config observer
|
||||
*/
|
||||
public static void addTransportConfigChangeObserver(ServerTransportConfigObserver observer) {
|
||||
AssertUtil.notNull(observer, "observer cannot be null");
|
||||
TRANSPORT_CONFIG_OBSERVERS.add(observer);
|
||||
}
|
||||
|
||||
private static class ServerNamespaceSetPropertyListener implements PropertyListener<Set<String>> {
|
||||
|
||||
@Override
|
||||
public synchronized void configLoad(Set<String> set) {
|
||||
if (set == null || set.isEmpty()) {
|
||||
RecordLog.warn("[ClusterServerConfigManager] WARN: empty initial server namespace set");
|
||||
return;
|
||||
}
|
||||
applyNamespaceSetChange(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void configUpdate(Set<String> set) {
|
||||
// TODO: should debounce?
|
||||
applyNamespaceSetChange(set);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyNamespaceSetChange(Set<String> newSet) {
|
||||
if (newSet == null) {
|
||||
return;
|
||||
}
|
||||
RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: {}", newSet);
|
||||
if (newSet.isEmpty()) {
|
||||
ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE);
|
||||
return;
|
||||
}
|
||||
|
||||
newSet = new HashSet<>(newSet);
|
||||
// Always add the `default` namespace to the namespace set.
|
||||
newSet.add(ServerConstants.DEFAULT_NAMESPACE);
|
||||
|
||||
if (embedded) {
|
||||
// In embedded server mode, the server itself is also a part of service,
|
||||
// so it should be added to namespace set.
|
||||
// By default, the added namespace is the appName.
|
||||
newSet.add(ConfigSupplierRegistry.getNamespaceSupplier().get());
|
||||
}
|
||||
|
||||
Set<String> oldSet = ClusterServerConfigManager.namespaceSet;
|
||||
if (oldSet != null && !oldSet.isEmpty()) {
|
||||
for (String ns : oldSet) {
|
||||
// Remove the cluster rule property for deprecated namespace set.
|
||||
if (!newSet.contains(ns)) {
|
||||
ClusterFlowRuleManager.removeProperty(ns);
|
||||
ClusterParamFlowRuleManager.removeProperty(ns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClusterServerConfigManager.namespaceSet = newSet;
|
||||
for (String ns : newSet) {
|
||||
// Register the rule property if needed.
|
||||
ClusterFlowRuleManager.registerPropertyIfAbsent(ns);
|
||||
ClusterParamFlowRuleManager.registerPropertyIfAbsent(ns);
|
||||
// Initialize the global QPS limiter for the namespace.
|
||||
GlobalRequestLimiter.initIfAbsent(ns);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServerGlobalTransportPropertyListener implements PropertyListener<ServerTransportConfig> {
|
||||
|
||||
@Override
|
||||
public void configLoad(ServerTransportConfig config) {
|
||||
if (config == null) {
|
||||
RecordLog.warn("[ClusterServerConfigManager] Empty initial server transport config");
|
||||
return;
|
||||
}
|
||||
applyConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configUpdate(ServerTransportConfig config) {
|
||||
applyConfig(config);
|
||||
}
|
||||
|
||||
private synchronized void applyConfig(ServerTransportConfig config) {
|
||||
if (!isValidTransportConfig(config)) {
|
||||
RecordLog.warn(
|
||||
"[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: {}", config);
|
||||
return;
|
||||
}
|
||||
RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: {}", config);
|
||||
if (config.getIdleSeconds() != idleSeconds) {
|
||||
idleSeconds = config.getIdleSeconds();
|
||||
}
|
||||
updateTokenServer(config);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateTokenServer(ServerTransportConfig config) {
|
||||
int newPort = config.getPort();
|
||||
AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)");
|
||||
if (newPort == port) {
|
||||
return;
|
||||
}
|
||||
ClusterServerConfigManager.port = newPort;
|
||||
|
||||
for (ServerTransportConfigObserver observer : TRANSPORT_CONFIG_OBSERVERS) {
|
||||
observer.onTransportConfigChange(config);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServerGlobalFlowPropertyListener implements PropertyListener<ServerFlowConfig> {
|
||||
|
||||
@Override
|
||||
public void configUpdate(ServerFlowConfig config) {
|
||||
applyGlobalFlowConfig(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configLoad(ServerFlowConfig config) {
|
||||
applyGlobalFlowConfig(config);
|
||||
}
|
||||
|
||||
private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) {
|
||||
if (!isValidFlowConfig(config)) {
|
||||
RecordLog.warn(
|
||||
"[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: {}", config);
|
||||
return;
|
||||
}
|
||||
RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: {}", config);
|
||||
if (config.getExceedCount() != exceedCount) {
|
||||
exceedCount = config.getExceedCount();
|
||||
}
|
||||
if (config.getMaxOccupyRatio() != maxOccupyRatio) {
|
||||
maxOccupyRatio = config.getMaxOccupyRatio();
|
||||
}
|
||||
if (config.getMaxAllowedQps() != maxAllowedQps) {
|
||||
maxAllowedQps = config.getMaxAllowedQps();
|
||||
GlobalRequestLimiter.applyMaxQpsChange(maxAllowedQps);
|
||||
}
|
||||
int newIntervalMs = config.getIntervalMs();
|
||||
int newSampleCount = config.getSampleCount();
|
||||
if (newIntervalMs != intervalMs || newSampleCount != sampleCount) {
|
||||
if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) {
|
||||
RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count");
|
||||
} else {
|
||||
intervalMs = newIntervalMs;
|
||||
sampleCount = newSampleCount;
|
||||
// Reset all the metrics.
|
||||
ClusterMetricStatistics.resetFlowMetrics();
|
||||
ClusterParamMetricStatistics.resetFlowMetrics();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidTransportConfig(ServerTransportConfig config) {
|
||||
return config != null && config.getPort() > 0 && config.getPort() <= 65535;
|
||||
}
|
||||
|
||||
public static boolean isValidFlowConfig(ServerFlowConfig config) {
|
||||
return config != null && config.getMaxOccupyRatio() >= 0 && config.getExceedCount() >= 0
|
||||
&& config.getMaxAllowedQps() >= 0
|
||||
&& FlowRuleUtil.isWindowConfigValid(config.getSampleCount(), config.getIntervalMs());
|
||||
}
|
||||
|
||||
public static double getExceedCount(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace);
|
||||
if (config != null) {
|
||||
return config.getExceedCount();
|
||||
}
|
||||
return exceedCount;
|
||||
}
|
||||
|
||||
public static double getMaxOccupyRatio(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace);
|
||||
if (config != null) {
|
||||
return config.getMaxOccupyRatio();
|
||||
}
|
||||
return maxOccupyRatio;
|
||||
}
|
||||
|
||||
public static int getIntervalMs(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace);
|
||||
if (config != null) {
|
||||
return config.getIntervalMs();
|
||||
}
|
||||
return intervalMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sample count of provided namespace.
|
||||
*
|
||||
* @param namespace valid namespace
|
||||
* @return the sample count of namespace; if the namespace does not have customized value, use the global value
|
||||
*/
|
||||
public static int getSampleCount(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace);
|
||||
if (config != null) {
|
||||
return config.getSampleCount();
|
||||
}
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
public static double getMaxAllowedQps() {
|
||||
return maxAllowedQps;
|
||||
}
|
||||
|
||||
public static double getMaxAllowedQps(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace);
|
||||
if (config != null) {
|
||||
return config.getMaxAllowedQps();
|
||||
}
|
||||
return maxAllowedQps;
|
||||
}
|
||||
|
||||
public static double getExceedCount() {
|
||||
return exceedCount;
|
||||
}
|
||||
|
||||
public static double getMaxOccupyRatio() {
|
||||
return maxOccupyRatio;
|
||||
}
|
||||
|
||||
public static Set<String> getNamespaceSet() {
|
||||
return namespaceSet;
|
||||
}
|
||||
|
||||
public static int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public static int getIdleSeconds() {
|
||||
return idleSeconds;
|
||||
}
|
||||
|
||||
public static int getIntervalMs() {
|
||||
return intervalMs;
|
||||
}
|
||||
|
||||
public static int getSampleCount() {
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
public static void setNamespaceSet(Set<String> namespaceSet) {
|
||||
applyNamespaceSetChange(namespaceSet);
|
||||
}
|
||||
|
||||
public static boolean isEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Set the embedded mode flag for the token server. </p>
|
||||
* <p>
|
||||
* NOTE: developers SHOULD NOT manually invoke this method.
|
||||
* The embedded flag should be initialized by Sentinel when starting token server.
|
||||
* </p>
|
||||
*
|
||||
* @param embedded whether the token server is currently running in embedded mode
|
||||
*/
|
||||
public static void setEmbedded(boolean embedded) {
|
||||
ClusterServerConfigManager.embedded = embedded;
|
||||
}
|
||||
|
||||
public static void setMaxAllowedQps(double maxAllowedQps) {
|
||||
ClusterServerConfigManager.maxAllowedQps = maxAllowedQps;
|
||||
}
|
||||
|
||||
private ClusterServerConfigManager() {}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.server.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ServerFlowConfig {
|
||||
|
||||
public static final double DEFAULT_EXCEED_COUNT = 1.0d;
|
||||
public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d;
|
||||
|
||||
public static final int DEFAULT_INTERVAL_MS = 1000;
|
||||
public static final int DEFAULT_SAMPLE_COUNT= 10;
|
||||
public static final double DEFAULT_MAX_ALLOWED_QPS= 30000;
|
||||
|
||||
private final String namespace;
|
||||
|
||||
private double exceedCount = DEFAULT_EXCEED_COUNT;
|
||||
private double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO;
|
||||
private int intervalMs = DEFAULT_INTERVAL_MS;
|
||||
private int sampleCount = DEFAULT_SAMPLE_COUNT;
|
||||
|
||||
private double maxAllowedQps = DEFAULT_MAX_ALLOWED_QPS;
|
||||
|
||||
public ServerFlowConfig() {
|
||||
this(ServerConstants.DEFAULT_NAMESPACE);
|
||||
}
|
||||
|
||||
public ServerFlowConfig(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public double getExceedCount() {
|
||||
return exceedCount;
|
||||
}
|
||||
|
||||
public ServerFlowConfig setExceedCount(double exceedCount) {
|
||||
this.exceedCount = exceedCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getMaxOccupyRatio() {
|
||||
return maxOccupyRatio;
|
||||
}
|
||||
|
||||
public ServerFlowConfig setMaxOccupyRatio(double maxOccupyRatio) {
|
||||
this.maxOccupyRatio = maxOccupyRatio;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getIntervalMs() {
|
||||
return intervalMs;
|
||||
}
|
||||
|
||||
public ServerFlowConfig setIntervalMs(int intervalMs) {
|
||||
this.intervalMs = intervalMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSampleCount() {
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
public ServerFlowConfig setSampleCount(int sampleCount) {
|
||||
this.sampleCount = sampleCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getMaxAllowedQps() {
|
||||
return maxAllowedQps;
|
||||
}
|
||||
|
||||
public ServerFlowConfig setMaxAllowedQps(double maxAllowedQps) {
|
||||
this.maxAllowedQps = maxAllowedQps;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerFlowConfig{" +
|
||||
"namespace='" + namespace + '\'' +
|
||||
", exceedCount=" + exceedCount +
|
||||
", maxOccupyRatio=" + maxOccupyRatio +
|
||||
", intervalMs=" + intervalMs +
|
||||
", sampleCount=" + sampleCount +
|
||||
", maxAllowedQps=" + maxAllowedQps +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.server.config;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ServerTransportConfig {
|
||||
|
||||
public static final int DEFAULT_IDLE_SECONDS = 600;
|
||||
|
||||
private int port;
|
||||
private int idleSeconds;
|
||||
|
||||
public ServerTransportConfig() {
|
||||
this(ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT, DEFAULT_IDLE_SECONDS);
|
||||
}
|
||||
|
||||
public ServerTransportConfig(int port, int idleSeconds) {
|
||||
this.port = port;
|
||||
this.idleSeconds = idleSeconds;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public ServerTransportConfig setPort(int port) {
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getIdleSeconds() {
|
||||
return idleSeconds;
|
||||
}
|
||||
|
||||
public ServerTransportConfig setIdleSeconds(int idleSeconds) {
|
||||
this.idleSeconds = idleSeconds;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServerTransportConfig{" +
|
||||
"port=" + port +
|
||||
", idleSeconds=" + idleSeconds +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.server.config;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface ServerTransportConfigObserver {
|
||||
|
||||
/**
|
||||
* Callback on server transport config (e.g. port) change.
|
||||
*
|
||||
* @param config new server transport config
|
||||
*/
|
||||
void onTransportConfigChange(ServerTransportConfig config);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* @author xuyue
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface Connection extends AutoCloseable {
|
||||
|
||||
SocketAddress getLocalAddress();
|
||||
|
||||
int getRemotePort();
|
||||
|
||||
String getRemoteIP();
|
||||
|
||||
void refreshLastReadTime(long lastReadTime);
|
||||
|
||||
long getLastReadTime();
|
||||
|
||||
String getConnectionKey();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ConnectionDescriptor {
|
||||
|
||||
private String address;
|
||||
private String host;
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public ConnectionDescriptor setAddress(String address) {
|
||||
this.address = address;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public ConnectionDescriptor setHost(String host) {
|
||||
this.host = host;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) { return true; }
|
||||
if (o == null || getClass() != o.getClass()) { return false; }
|
||||
|
||||
ConnectionDescriptor that = (ConnectionDescriptor)o;
|
||||
|
||||
return Objects.equals(address, that.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address != null ? address.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConnectionDescriptor{" +
|
||||
"address='" + address + '\'' +
|
||||
", host='" + host + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* The connection group stores connection set for a specific namespace.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ConnectionGroup {
|
||||
|
||||
private final String namespace;
|
||||
|
||||
private final Set<ConnectionDescriptor> connectionSet = Collections.synchronizedSet(new HashSet<ConnectionDescriptor>());
|
||||
private final AtomicInteger connectedCount = new AtomicInteger();
|
||||
|
||||
public ConnectionGroup(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty");
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public ConnectionGroup() {
|
||||
this(ServerConstants.DEFAULT_NAMESPACE);
|
||||
}
|
||||
|
||||
public ConnectionGroup addConnection(String address) {
|
||||
AssertUtil.notEmpty(address, "address cannot be empty");
|
||||
|
||||
String[] ip = address.split(":");
|
||||
String host;
|
||||
if (ip != null && ip.length >= 1) {
|
||||
host = ip[0];
|
||||
} else {
|
||||
host = address;
|
||||
}
|
||||
boolean newAdded = connectionSet.add(new ConnectionDescriptor().setAddress(address).setHost(host));
|
||||
if (newAdded) {
|
||||
connectedCount.incrementAndGet();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConnectionGroup removeConnection(String address) {
|
||||
AssertUtil.notEmpty(address, "address cannot be empty");
|
||||
|
||||
if (connectionSet.remove(new ConnectionDescriptor().setAddress(address))) {
|
||||
connectedCount.decrementAndGet();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public Set<ConnectionDescriptor> getConnectionSet() {
|
||||
return connectionSet;
|
||||
}
|
||||
|
||||
public int getConnectedCount() {
|
||||
return connectedCount.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* Manager for namespace-scope {@link ConnectionGroup}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ConnectionManager {
|
||||
|
||||
/**
|
||||
* Connection map (namespace, connection).
|
||||
*/
|
||||
private static final Map<String, ConnectionGroup> CONN_MAP = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* namespace map (address, namespace).
|
||||
*/
|
||||
private static final Map<String, String> NAMESPACE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Get connected count for specific namespace.
|
||||
*
|
||||
* @param namespace namespace to check
|
||||
* @return connected count for specific namespace
|
||||
*/
|
||||
public static int getConnectedCount(String namespace) {
|
||||
AssertUtil.notEmpty(namespace, "namespace should not be empty");
|
||||
ConnectionGroup group = CONN_MAP.get(namespace);
|
||||
return group == null ? 0 : group.getConnectedCount();
|
||||
}
|
||||
|
||||
public static ConnectionGroup getOrCreateGroup(String namespace) {
|
||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty");
|
||||
ConnectionGroup group = CONN_MAP.get(namespace);
|
||||
if (group == null) {
|
||||
synchronized (CREATE_LOCK) {
|
||||
if ((group = CONN_MAP.get(namespace)) == null) {
|
||||
group = new ConnectionGroup(namespace);
|
||||
CONN_MAP.put(namespace, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
public static void removeConnection(String address) {
|
||||
AssertUtil.assertNotBlank(address, "address should not be empty");
|
||||
String namespace = NAMESPACE_MAP.get(address);
|
||||
if (namespace != null) {
|
||||
ConnectionGroup group = CONN_MAP.get(namespace);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
group.removeConnection(address);
|
||||
RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace);
|
||||
}
|
||||
NAMESPACE_MAP.remove(address);
|
||||
}
|
||||
|
||||
public static void removeConnection(String namespace, String address) {
|
||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty");
|
||||
AssertUtil.assertNotBlank(address, "address should not be empty");
|
||||
ConnectionGroup group = CONN_MAP.get(namespace);
|
||||
if (group == null) {
|
||||
return;
|
||||
}
|
||||
group.removeConnection(address);
|
||||
NAMESPACE_MAP.remove(address);
|
||||
RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace);
|
||||
}
|
||||
|
||||
public static ConnectionGroup addConnection(String namespace, String address) {
|
||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty");
|
||||
AssertUtil.assertNotBlank(address, "address should not be empty");
|
||||
ConnectionGroup group = getOrCreateGroup(namespace);
|
||||
group.addConnection(address);
|
||||
NAMESPACE_MAP.put(address, namespace);
|
||||
RecordLog.info("[ConnectionManager] Client <{}> registered with namespace <{}>", address, namespace);
|
||||
return group;
|
||||
}
|
||||
|
||||
public static ConnectionGroup getOrCreateConnectionGroup(String namespace) {
|
||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty");
|
||||
ConnectionGroup group = getOrCreateGroup(namespace);
|
||||
return group;
|
||||
}
|
||||
|
||||
public static ConnectionGroup getConnectionGroup(String namespace) {
|
||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty");
|
||||
ConnectionGroup group = CONN_MAP.get(namespace);
|
||||
return group;
|
||||
}
|
||||
|
||||
public static boolean isClientOnline(String address){
|
||||
return NAMESPACE_MAP.containsKey(address);
|
||||
}
|
||||
static void clear() {
|
||||
CONN_MAP.clear();
|
||||
NAMESPACE_MAP.clear();
|
||||
}
|
||||
|
||||
private static final Object CREATE_LOCK = new Object();
|
||||
|
||||
private ConnectionManager() {}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* Universal connection pool for connection management.
|
||||
*
|
||||
* @author xuyue
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ConnectionPool {
|
||||
|
||||
@SuppressWarnings("PMD.ThreadPoolCreationRule")
|
||||
private static final ScheduledExecutorService TIMER = Executors.newScheduledThreadPool(2);
|
||||
|
||||
/**
|
||||
* Format: ("ip:port", connection)
|
||||
*/
|
||||
private final Map<String, Connection> CONNECTION_MAP = new ConcurrentHashMap<String, Connection>();
|
||||
|
||||
/**
|
||||
* Periodic scan task.
|
||||
*/
|
||||
private ScheduledFuture scanTaskFuture = null;
|
||||
|
||||
public void createConnection(Channel channel) {
|
||||
if (channel != null) {
|
||||
Connection connection = new NettyConnection(channel, this);
|
||||
|
||||
String connKey = getConnectionKey(channel);
|
||||
CONNECTION_MAP.put(connKey, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the scan task for long-idle connections.
|
||||
*/
|
||||
private synchronized void startScan() {
|
||||
if (scanTaskFuture == null
|
||||
|| scanTaskFuture.isCancelled()
|
||||
|| scanTaskFuture.isDone()) {
|
||||
scanTaskFuture = TIMER.scheduleAtFixedRate(
|
||||
new ScanIdleConnectionTask(this), 10, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format to "ip:port".
|
||||
*
|
||||
* @param channel channel
|
||||
* @return formatted key
|
||||
*/
|
||||
private String getConnectionKey(Channel channel) {
|
||||
InetSocketAddress socketAddress = (InetSocketAddress)channel.remoteAddress();
|
||||
String remoteIp = socketAddress.getAddress().getHostAddress();
|
||||
int remotePort = socketAddress.getPort();
|
||||
return remoteIp + ":" + remotePort;
|
||||
}
|
||||
|
||||
private String getConnectionKey(String ip, int port) {
|
||||
return ip + ":" + port;
|
||||
}
|
||||
|
||||
public void refreshLastReadTime(Channel channel) {
|
||||
if (channel != null) {
|
||||
String connKey = getConnectionKey(channel);
|
||||
Connection connection = CONNECTION_MAP.get(connKey);
|
||||
if (connection != null) {
|
||||
connection.refreshLastReadTime(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnection(String remoteIp, int remotePort) {
|
||||
String connKey = getConnectionKey(remoteIp, remotePort);
|
||||
return CONNECTION_MAP.get(connKey);
|
||||
}
|
||||
|
||||
public void remove(Channel channel) {
|
||||
String connKey = getConnectionKey(channel);
|
||||
CONNECTION_MAP.remove(connKey);
|
||||
}
|
||||
|
||||
public List<Connection> listAllConnection() {
|
||||
List<Connection> connections = new ArrayList<Connection>(CONNECTION_MAP.values());
|
||||
return connections;
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return CONNECTION_MAP.size();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
CONNECTION_MAP.clear();
|
||||
}
|
||||
|
||||
public void shutdownAll() throws Exception {
|
||||
for (Connection c : CONNECTION_MAP.values()) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshIdleTask() {
|
||||
if (scanTaskFuture == null || scanTaskFuture.cancel(false)) {
|
||||
startScan();
|
||||
} else {
|
||||
RecordLog.info("The result of canceling scanTask is error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.server.connection;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* @author xuyue
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class NettyConnection implements Connection {
|
||||
|
||||
private String remoteIp;
|
||||
private int remotePort;
|
||||
private Channel channel;
|
||||
|
||||
private long lastReadTime;
|
||||
|
||||
private ConnectionPool pool;
|
||||
|
||||
public NettyConnection(Channel channel, ConnectionPool pool) {
|
||||
this.channel = channel;
|
||||
this.pool = pool;
|
||||
|
||||
InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
|
||||
this.remoteIp = socketAddress.getAddress().getHostAddress();
|
||||
this.remotePort = socketAddress.getPort();
|
||||
this.lastReadTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getLocalAddress() {
|
||||
return channel.localAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemotePort() {
|
||||
return remotePort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteIP() {
|
||||
return remoteIp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshLastReadTime(long lastReadTime) {
|
||||
this.lastReadTime = lastReadTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastReadTime() {
|
||||
return lastReadTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionKey() {
|
||||
return remoteIp + ":" + remotePort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Remove from connection pool.
|
||||
pool.remove(channel);
|
||||
// Close the connection.
|
||||
if (channel != null && channel.isActive()){
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.alibaba.csp.sentinel.cluster.server.connection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* @author xuyue
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ScanIdleConnectionTask implements Runnable {
|
||||
|
||||
private final ConnectionPool connectionPool;
|
||||
|
||||
public ScanIdleConnectionTask(ConnectionPool connectionPool) {
|
||||
this.connectionPool = connectionPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int idleSeconds = ClusterServerConfigManager.getIdleSeconds();
|
||||
long idleTimeMillis = idleSeconds * 1000;
|
||||
if (idleTimeMillis < 0) {
|
||||
idleTimeMillis = ServerTransportConfig.DEFAULT_IDLE_SECONDS * 1000;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
List<Connection> connections = connectionPool.listAllConnection();
|
||||
for (Connection conn : connections) {
|
||||
if ((now - conn.getLastReadTime()) > idleTimeMillis) {
|
||||
RecordLog.info("[ScanIdleConnectionTask] The connection <{}:{}> has been idle for <{}>s. It will be closed now.",
|
||||
conn.getRemoteIP(), conn.getRemotePort(), idleSeconds);
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
RecordLog.warn("[ScanIdleConnectionTask] Failed to clean-up idle tasks", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.server.handler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool;
|
||||
import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor;
|
||||
import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
/**
|
||||
* Netty server handler for Sentinel token server.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class TokenServerHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final ConnectionPool globalConnectionPool;
|
||||
|
||||
public TokenServerHandler(ConnectionPool globalConnectionPool) {
|
||||
this.globalConnectionPool = globalConnectionPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
globalConnectionPool.createConnection(ctx.channel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
String remoteAddress = getRemoteAddress(ctx);
|
||||
globalConnectionPool.remove(ctx.channel());
|
||||
ConnectionManager.removeConnection(remoteAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
globalConnectionPool.refreshLastReadTime(ctx.channel());
|
||||
if (msg instanceof ClusterRequest) {
|
||||
ClusterRequest request = (ClusterRequest)msg;
|
||||
|
||||
// Client ping with its namespace, add to connection manager.
|
||||
if (request.getType() == ClusterConstants.MSG_TYPE_PING) {
|
||||
handlePingRequest(ctx, request);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick request processor for request type.
|
||||
RequestProcessor<?, ?> processor = RequestProcessorProvider.getProcessor(request.getType());
|
||||
if (processor == null) {
|
||||
RecordLog.warn("[TokenServerHandler] No processor for request type: " + request.getType());
|
||||
writeBadResponse(ctx, request);
|
||||
} else {
|
||||
ClusterResponse<?> response = processor.processRequest(request);
|
||||
writeResponse(ctx, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBadResponse(ChannelHandlerContext ctx, ClusterRequest request) {
|
||||
ClusterResponse<?> response = new ClusterResponse<>(request.getId(), request.getType(),
|
||||
ClusterConstants.RESPONSE_STATUS_BAD, null);
|
||||
writeResponse(ctx, response);
|
||||
}
|
||||
|
||||
private void writeResponse(ChannelHandlerContext ctx, ClusterResponse response) {
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
|
||||
private void handlePingRequest(ChannelHandlerContext ctx, ClusterRequest request) {
|
||||
if (request.getData() == null || StringUtil.isBlank((String)request.getData())) {
|
||||
writeBadResponse(ctx, request);
|
||||
return;
|
||||
}
|
||||
String namespace = (String)request.getData();
|
||||
String clientAddress = getRemoteAddress(ctx);
|
||||
// Add the remote namespace to connection manager.
|
||||
int curCount = ConnectionManager.addConnection(namespace, clientAddress).getConnectedCount();
|
||||
int status = ClusterConstants.RESPONSE_STATUS_OK;
|
||||
ClusterResponse<Integer> response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount);
|
||||
writeResponse(ctx, response);
|
||||
}
|
||||
|
||||
private String getRemoteAddress(ChannelHandlerContext ctx) {
|
||||
if (ctx.channel().remoteAddress() == null) {
|
||||
return null;
|
||||
}
|
||||
InetSocketAddress inetAddress = (InetSocketAddress) ctx.channel().remoteAddress();
|
||||
return inetAddress.getAddress().getHostAddress() + ":" + inetAddress.getPort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.server.init;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowRequestDataDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowResponseDataWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.data.ParamFlowRequestDataDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.data.PingRequestDataDecoder;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.data.PingResponseDataWriter;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry;
|
||||
import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
|
||||
import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider;
|
||||
import com.alibaba.csp.sentinel.init.InitFunc;
|
||||
import com.alibaba.csp.sentinel.log.RecordLog;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class DefaultClusterServerInitFunc implements InitFunc {
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
initDefaultEntityDecoders();
|
||||
initDefaultEntityWriters();
|
||||
|
||||
initDefaultProcessors();
|
||||
|
||||
// Eagerly-trigger the SPI pre-load of token service.
|
||||
TokenServiceProvider.getService();
|
||||
|
||||
RecordLog.info("[DefaultClusterServerInitFunc] Default entity codec and processors registered");
|
||||
}
|
||||
|
||||
private void initDefaultEntityWriters() {
|
||||
ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PING, new PingResponseDataWriter());
|
||||
ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_FLOW, new FlowResponseDataWriter());
|
||||
ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PARAM_FLOW, new FlowResponseDataWriter());
|
||||
}
|
||||
|
||||
private void initDefaultEntityDecoders() {
|
||||
RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PING, new PingRequestDataDecoder());
|
||||
RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_FLOW, new FlowRequestDataDecoder());
|
||||
RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PARAM_FLOW, new ParamFlowRequestDataDecoder());
|
||||
}
|
||||
|
||||
private void initDefaultProcessors() {
|
||||
// Eagerly-trigger the SPI pre-load.
|
||||
RequestProcessorProvider.getProcessor(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.server.log;
|
||||
|
||||
import com.alibaba.csp.sentinel.eagleeye.EagleEye;
|
||||
import com.alibaba.csp.sentinel.eagleeye.StatLogger;
|
||||
import com.alibaba.csp.sentinel.log.LogBase;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterServerStatLogUtil {
|
||||
|
||||
private static final String FILE_NAME = "sentinel-server.log";
|
||||
|
||||
private static StatLogger statLogger;
|
||||
|
||||
static {
|
||||
String path = LogBase.getLogBaseDir() + FILE_NAME;
|
||||
|
||||
statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-server-record")
|
||||
.intervalSeconds(1)
|
||||
.entryDelimiter('|')
|
||||
.keyDelimiter(',')
|
||||
.valueDelimiter(',')
|
||||
.maxEntryCount(5000)
|
||||
.configLogFilePath(path)
|
||||
.maxFileSizeMB(300)
|
||||
.maxBackupIndex(3)
|
||||
.buildSingleton();
|
||||
}
|
||||
|
||||
public static void log(String msg) {
|
||||
statLogger.stat(msg).count();
|
||||
}
|
||||
|
||||
public static void log(String msg, int count) {
|
||||
statLogger.stat(msg).count(count);
|
||||
}
|
||||
|
||||
private ClusterServerStatLogUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.server.processor;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
import com.alibaba.csp.sentinel.cluster.annotation.RequestType;
|
||||
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
|
||||
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
|
||||
import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@RequestType(ClusterConstants.MSG_TYPE_FLOW)
|
||||
public class FlowRequestProcessor implements RequestProcessor<FlowRequestData, FlowTokenResponseData> {
|
||||
|
||||
@Override
|
||||
public ClusterResponse<FlowTokenResponseData> processRequest(ClusterRequest<FlowRequestData> request) {
|
||||
TokenService tokenService = TokenServiceProvider.getService();
|
||||
|
||||
long flowId = request.getData().getFlowId();
|
||||
int count = request.getData().getCount();
|
||||
boolean prioritized = request.getData().isPriority();
|
||||
|
||||
TokenResult result = tokenService.requestToken(flowId, count, prioritized);
|
||||
return toResponse(result, request);
|
||||
}
|
||||
|
||||
private ClusterResponse<FlowTokenResponseData> toResponse(TokenResult result, ClusterRequest request) {
|
||||
return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(),
|
||||
new FlowTokenResponseData()
|
||||
.setRemainingCount(result.getRemaining())
|
||||
.setWaitInMs(result.getWaitInMs())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.server.processor;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenService;
|
||||
import com.alibaba.csp.sentinel.cluster.annotation.RequestType;
|
||||
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
|
||||
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
|
||||
import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@RequestType(ClusterConstants.MSG_TYPE_PARAM_FLOW)
|
||||
public class ParamFlowRequestProcessor implements RequestProcessor<ParamFlowRequestData, FlowTokenResponseData> {
|
||||
|
||||
@Override
|
||||
public ClusterResponse<FlowTokenResponseData> processRequest(ClusterRequest<ParamFlowRequestData> request) {
|
||||
TokenService tokenService = TokenServiceProvider.getService();
|
||||
|
||||
long flowId = request.getData().getFlowId();
|
||||
int count = request.getData().getCount();
|
||||
Collection<Object> args = request.getData().getParams();
|
||||
|
||||
TokenResult result = tokenService.requestParamToken(flowId, count, args);
|
||||
return toResponse(result, request);
|
||||
}
|
||||
|
||||
private ClusterResponse<FlowTokenResponseData> toResponse(TokenResult result, ClusterRequest request) {
|
||||
return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(),
|
||||
new FlowTokenResponseData()
|
||||
.setRemainingCount(result.getRemaining())
|
||||
.setWaitInMs(0)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.server.processor;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
|
||||
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
|
||||
|
||||
/**
|
||||
* Interface of cluster request processor.
|
||||
*
|
||||
* @param <T> type of request body
|
||||
* @param <R> type of response body
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public interface RequestProcessor<T, R> {
|
||||
|
||||
/**
|
||||
* Process the cluster request.
|
||||
*
|
||||
* @param request Sentinel cluster request
|
||||
* @return the response after processed
|
||||
*/
|
||||
ClusterResponse<R> processRequest(ClusterRequest<T> request);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.server.processor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.annotation.RequestType;
|
||||
import com.alibaba.csp.sentinel.spi.SpiLoader;
|
||||
import com.alibaba.csp.sentinel.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class RequestProcessorProvider {
|
||||
|
||||
private static final Map<Integer, RequestProcessor> PROCESSOR_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
loadAndInit();
|
||||
}
|
||||
|
||||
private static void loadAndInit() {
|
||||
List<RequestProcessor> processors = SpiLoader.of(RequestProcessor.class).loadInstanceList();
|
||||
for (RequestProcessor processor : processors) {
|
||||
Integer type = parseRequestType(processor);
|
||||
if (type != null) {
|
||||
PROCESSOR_MAP.put(type, processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer parseRequestType(RequestProcessor processor) {
|
||||
RequestType requestType = processor.getClass().getAnnotation(RequestType.class);
|
||||
if (requestType != null) {
|
||||
return requestType.value();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static RequestProcessor getProcessor(int type) {
|
||||
return PROCESSOR_MAP.get(type);
|
||||
}
|
||||
|
||||
static void addProcessorIfAbsent(int type, RequestProcessor processor) {
|
||||
PROCESSOR_MAP.putIfAbsent(type, processor);
|
||||
}
|
||||
|
||||
static void addProcessor(int type, RequestProcessor processor) {
|
||||
AssertUtil.notNull(processor, "processor cannot be null");
|
||||
PROCESSOR_MAP.put(type, processor);
|
||||
}
|
||||
|
||||
private RequestProcessorProvider() {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.server.util;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterRuleUtil {
|
||||
|
||||
public static boolean validId(Long id) {
|
||||
return id != null && id > 0;
|
||||
}
|
||||
|
||||
private ClusterRuleUtil() {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.codec.DefaultRequestEntityDecoder
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.codec.DefaultResponseEntityWriter
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.DefaultEmbeddedTokenServer
|
||||
@@ -0,0 +1,2 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor
|
||||
com.alibaba.csp.sentinel.cluster.server.processor.ParamFlowRequestProcessor
|
||||
@@ -0,0 +1,10 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerFlowConfigHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterParamFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerConfigHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerTransportConfigHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyServerNamespaceSetHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterParamFlowRulesCommandHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerInfoCommandHandler
|
||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterMetricCommandHandler
|
||||
@@ -0,0 +1 @@
|
||||
com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Useful for testing clustered flow control.
|
||||
* Only used for test.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public final class ClusterFlowTestUtil {
|
||||
|
||||
public static void assertResultPass(TokenResult result) {
|
||||
assertNotNull(result);
|
||||
assertEquals(TokenResultStatus.OK, (int) result.getStatus());
|
||||
}
|
||||
|
||||
public static void assertResultBlock(TokenResult result) {
|
||||
assertNotNull(result);
|
||||
assertEquals(TokenResultStatus.BLOCKED, (int) result.getStatus());
|
||||
}
|
||||
|
||||
public static void assertResultWait(TokenResult result, int waitInMs) {
|
||||
assertNotNull(result);
|
||||
assertEquals(TokenResultStatus.SHOULD_WAIT, (int) result.getStatus());
|
||||
assertEquals(waitInMs, result.getWaitInMs());
|
||||
}
|
||||
|
||||
public static void sleep(int t) {
|
||||
try {
|
||||
Thread.sleep(t);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private ClusterFlowTestUtil() {}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.flow.statistic.ClusterMetricStatistics;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
|
||||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public class ClusterFlowCheckerTest {
|
||||
|
||||
//@Test
|
||||
public void testAcquireClusterTokenOccupyPass() {
|
||||
long flowId = 98765L;
|
||||
final int threshold = 5;
|
||||
FlowRule clusterRule = new FlowRule("abc")
|
||||
.setCount(threshold)
|
||||
.setClusterMode(true)
|
||||
.setClusterConfig(new ClusterFlowConfig()
|
||||
.setFlowId(flowId)
|
||||
.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL));
|
||||
int sampleCount = 5;
|
||||
int intervalInMs = 1000;
|
||||
int bucketLength = intervalInMs / sampleCount;
|
||||
ClusterMetric metric = new ClusterMetric(sampleCount, intervalInMs);
|
||||
ClusterMetricStatistics.putMetric(flowId, metric);
|
||||
|
||||
System.out.println(System.currentTimeMillis());
|
||||
assertResultPass(tryAcquire(clusterRule, false));
|
||||
assertResultPass(tryAcquire(clusterRule, false));
|
||||
sleep(bucketLength);
|
||||
assertResultPass(tryAcquire(clusterRule, false));
|
||||
sleep(bucketLength);
|
||||
assertResultPass(tryAcquire(clusterRule, true));
|
||||
assertResultPass(tryAcquire(clusterRule, false));
|
||||
assertResultBlock(tryAcquire(clusterRule, true));
|
||||
sleep(bucketLength);
|
||||
assertResultBlock(tryAcquire(clusterRule, false));
|
||||
assertResultBlock(tryAcquire(clusterRule, false));
|
||||
sleep(bucketLength);
|
||||
assertResultBlock(tryAcquire(clusterRule, false));
|
||||
assertResultWait(tryAcquire(clusterRule, true), bucketLength);
|
||||
assertResultBlock(tryAcquire(clusterRule, false));
|
||||
sleep(bucketLength);
|
||||
assertResultPass(tryAcquire(clusterRule, false));
|
||||
|
||||
ClusterMetricStatistics.removeMetric(flowId);
|
||||
}
|
||||
|
||||
private TokenResult tryAcquire(FlowRule clusterRule, boolean occupy) {
|
||||
return ClusterFlowChecker.acquireClusterToken(clusterRule, 1, occupy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.TokenCacheNodeManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager;
|
||||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author yunfeiyanggzq
|
||||
*/
|
||||
public class ConcurrentClusterFlowCheckerTest extends AbstractTimeBasedTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
FlowRule rule = new FlowRule();
|
||||
ClusterFlowConfig config = new ClusterFlowConfig();
|
||||
config.setResourceTimeout(500);
|
||||
config.setClientOfflineTime(1000);
|
||||
config.setFlowId(111L);
|
||||
config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL);
|
||||
rule.setClusterConfig(config);
|
||||
rule.setClusterMode(true);
|
||||
rule.setCount(10);
|
||||
rule.setResource("test");
|
||||
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
|
||||
ArrayList<FlowRule> rules = new ArrayList<>();
|
||||
rules.add(rule);
|
||||
ClusterFlowRuleManager.registerPropertyIfAbsent("1-name");
|
||||
ClusterFlowRuleManager.loadRules("1-name", rules);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEasyAcquireAndRelease() throws InterruptedException {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L);
|
||||
ArrayList<Long> list = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1);
|
||||
Assert.assertTrue("fail to acquire token",
|
||||
result.getStatus() == TokenResultStatus.OK && result.getTokenId() != 0);
|
||||
list.add(result.getTokenId());
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1);
|
||||
Assert.assertTrue("fail to acquire block token",
|
||||
result.getStatus() == TokenResultStatus.BLOCKED);
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
TokenResult result = ConcurrentClusterFlowChecker.releaseConcurrentToken(list.get(i));
|
||||
Assert.assertTrue("fail to release token",
|
||||
result.getStatus() == TokenResultStatus.RELEASE_OK);
|
||||
}
|
||||
Assert.assertTrue("fail to release token",
|
||||
CurrentConcurrencyManager.get(111L).get() == 0 && TokenCacheNodeManager.getSize() == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcurrentAcquireAndRelease() throws InterruptedException {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
final FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L);
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1000);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(100);
|
||||
|
||||
for (long i = 0; i < 1000; i++) {
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assert rule != null;
|
||||
TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1);
|
||||
Assert.assertTrue("concurrent control fail", CurrentConcurrencyManager.get(111L).get() <= rule.getCount());
|
||||
if (result.getStatus() == TokenResultStatus.OK) {
|
||||
ConcurrentClusterFlowChecker.releaseConcurrentToken(result.getTokenId());
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
};
|
||||
pool.execute(task);
|
||||
}
|
||||
countDownLatch.await();
|
||||
pool.shutdown();
|
||||
assert rule != null;
|
||||
Assert.assertTrue("fail to acquire and release token",
|
||||
CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseExpiredToken() throws InterruptedException {
|
||||
ConnectionManager.addConnection("test", "127.0.0.1");
|
||||
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1);
|
||||
}
|
||||
Thread.sleep(3000);
|
||||
Assert.assertTrue("fail to acquire and release token", CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.CurrentConcurrencyManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class CurrentConcurrencyManagerTest {
|
||||
@Test
|
||||
public void updateTest() throws InterruptedException {
|
||||
CurrentConcurrencyManager.put(111L, 0);
|
||||
CurrentConcurrencyManager.put(222L, 0);
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1000);
|
||||
ExecutorService pool = Executors.newFixedThreadPool(100);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CurrentConcurrencyManager.addConcurrency(111L, 1);
|
||||
CurrentConcurrencyManager.addConcurrency(222L, 2);
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
};
|
||||
pool.execute(task);
|
||||
}
|
||||
countDownLatch.await();
|
||||
pool.shutdown();
|
||||
Assert.assertEquals(1000, CurrentConcurrencyManager.get(111L).get());
|
||||
Assert.assertEquals(2000, CurrentConcurrencyManager.get(222L).get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.rule.ClusterFlowRuleManager;
|
||||
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.slots.block.ClusterRuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TokenCacheNodeManagerTest extends AbstractTimeBasedTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
FlowRule rule = new FlowRule();
|
||||
ClusterFlowConfig config = new ClusterFlowConfig();
|
||||
config.setResourceTimeout(500);
|
||||
config.setClientOfflineTime(1000);
|
||||
config.setFlowId(111L);
|
||||
config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL);
|
||||
rule.setClusterConfig(config);
|
||||
rule.setClusterMode(true);
|
||||
rule.setCount(10);
|
||||
rule.setResource("test");
|
||||
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
|
||||
ArrayList<FlowRule> rules = new ArrayList<>();
|
||||
rules.add(rule);
|
||||
ClusterFlowRuleManager.registerPropertyIfAbsent("1-name");
|
||||
ClusterFlowRuleManager.loadRules("1-name", rules);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutTokenCacheNode() throws InterruptedException {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
|
||||
for (long i = 0; i < 100; i++) {
|
||||
final TokenCacheNode node = new TokenCacheNode();
|
||||
node.setTokenId(i);
|
||||
node.setFlowId(111L);
|
||||
node.setResourceTimeout(10000L);
|
||||
node.setClientTimeout(10000L);
|
||||
node.setClientAddress("localhost");
|
||||
if (TokenCacheNodeManager.validToken(node)) {
|
||||
TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node);
|
||||
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(100, TokenCacheNodeManager.getSize());
|
||||
for (int i = 0; i < 100; i++) {
|
||||
TokenCacheNodeManager.getTokenCacheNode((long) (Math.random() * 100));
|
||||
}
|
||||
List<Long> keyList = new ArrayList<>(TokenCacheNodeManager.getCacheKeySet());
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Assert.assertEquals(i, (long) keyList.get(i));
|
||||
TokenCacheNodeManager.removeTokenCacheNode(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.limit;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
|
||||
import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GlobalRequestLimiterTest extends AbstractTimeBasedTest {
|
||||
@Before
|
||||
public void preTest() {
|
||||
ClusterServerConfigManager.setMaxAllowedQps(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass() throws InterruptedException {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
GlobalRequestLimiter.initIfAbsent("user");
|
||||
Assert.assertNotNull(GlobalRequestLimiter.getRequestLimiter("user"));
|
||||
Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("user"), 0.01);
|
||||
Assert.assertTrue(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertTrue(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertTrue(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertFalse(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertEquals(3, GlobalRequestLimiter.getCurrentQps("user"), 0.01);
|
||||
|
||||
// wait a second to refresh the window
|
||||
sleep(1000);
|
||||
Assert.assertTrue(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertTrue(GlobalRequestLimiter.tryPass("user"));
|
||||
Assert.assertEquals(2, GlobalRequestLimiter.getCurrentQps("user"), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeMaxAllowedQps() {
|
||||
GlobalRequestLimiter.initIfAbsent("foo");
|
||||
Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01);
|
||||
GlobalRequestLimiter.applyMaxQpsChange(10);
|
||||
Assert.assertEquals(10, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.limit;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class RequestLimiterTest extends AbstractTimeBasedTest {
|
||||
|
||||
@Test
|
||||
public void testRequestLimiter() {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
RequestLimiter limiter = new RequestLimiter(10);
|
||||
limiter.add(3);
|
||||
limiter.add(3);
|
||||
limiter.add(3);
|
||||
assertTrue(limiter.canPass());
|
||||
assertEquals(9, limiter.getSum());
|
||||
limiter.add(3);
|
||||
assertFalse(limiter.canPass());
|
||||
|
||||
// wait a second to refresh the window
|
||||
sleep(1000);
|
||||
limiter.add(3);
|
||||
assertTrue(limiter.tryPass());
|
||||
assertTrue(limiter.canPass());
|
||||
assertEquals(4, limiter.getSum());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
|
||||
import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ClusterMetricTest extends AbstractTimeBasedTest {
|
||||
|
||||
@Test
|
||||
public void testTryOccupyNext() {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
ClusterMetric metric = new ClusterMetric(5, 25);
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
metric.add(ClusterFlowEvent.PASS, 2);
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
metric.add(ClusterFlowEvent.BLOCK, 1);
|
||||
Assert.assertEquals(4, metric.getSum(ClusterFlowEvent.PASS));
|
||||
Assert.assertEquals(1, metric.getSum(ClusterFlowEvent.BLOCK));
|
||||
Assert.assertEquals(160, metric.getAvg(ClusterFlowEvent.PASS), 0.01);
|
||||
Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 111, 900));
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
metric.add(ClusterFlowEvent.PASS, 2);
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 222, 900));
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
metric.add(ClusterFlowEvent.PASS, 2);
|
||||
metric.add(ClusterFlowEvent.PASS, 1);
|
||||
Assert.assertEquals(0, metric.tryOccupyNext(ClusterFlowEvent.PASS, 333, 900));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.metric;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClusterParamMetricTest extends AbstractTimeBasedTest {
|
||||
|
||||
@Test
|
||||
public void testClusterParamMetric() {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
Map<Object, Double> topMap = new HashMap<Object, Double>();
|
||||
ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100);
|
||||
metric.addValue("e1", -1);
|
||||
metric.addValue("e1", -2);
|
||||
metric.addValue("e2", 100);
|
||||
metric.addValue("e2", 23);
|
||||
metric.addValue("e3", 100);
|
||||
metric.addValue("e3", 230);
|
||||
Assert.assertEquals(-3, metric.getSum("e1"));
|
||||
Assert.assertEquals(-120, metric.getAvg("e1"), 0.01);
|
||||
topMap.put("e3", (double) 13200);
|
||||
Assert.assertEquals(topMap, metric.getTopValues(1));
|
||||
topMap.put("e2", (double) 4920);
|
||||
topMap.put("e1", (double) -120);
|
||||
Assert.assertEquals(topMap, metric.getTopValues(5));
|
||||
metric.addValue("e2", 100);
|
||||
metric.addValue("e2", 23);
|
||||
Assert.assertEquals(246, metric.getSum("e2"));
|
||||
Assert.assertEquals(9840, metric.getAvg("e2"), 0.01);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIllegalArgument() {
|
||||
ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100);
|
||||
metric.getTopValues(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.server.codec.data;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test cases for {@link PingResponseDataWriter}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class PingResponseDataWriterTest {
|
||||
|
||||
@Test
|
||||
public void testWritePingResponseAndParse() {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
PingResponseDataWriter writer = new PingResponseDataWriter();
|
||||
|
||||
int small = 120;
|
||||
writer.writeTo(small, buf);
|
||||
assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4);
|
||||
assertThat(buf.readInt()).isEqualTo(small);
|
||||
|
||||
int big = Integer.MAX_VALUE;
|
||||
writer.writeTo(big, buf);
|
||||
assertThat(buf.readableBytes()).isGreaterThanOrEqualTo(4);
|
||||
assertThat(buf.readInt()).isEqualTo(big);
|
||||
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.alibaba.csp.sentinel.cluster.server.config;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class ClusterServerConfigManagerTest {
|
||||
|
||||
@Test
|
||||
public void testIsValidTransportConfig() {
|
||||
ServerTransportConfig badConfig1 = new ServerTransportConfig().setPort(-1);
|
||||
ServerTransportConfig badConfig2 = new ServerTransportConfig().setPort(886622);
|
||||
ServerTransportConfig goodConfig1 = new ServerTransportConfig().setPort(23456);
|
||||
assertFalse(ClusterServerConfigManager.isValidTransportConfig(badConfig1));
|
||||
assertFalse(ClusterServerConfigManager.isValidTransportConfig(badConfig2));
|
||||
assertTrue(ClusterServerConfigManager.isValidTransportConfig(goodConfig1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidFlowConfig() {
|
||||
ServerFlowConfig badConfig1 = new ServerFlowConfig().setMaxAllowedQps(-2);
|
||||
ServerFlowConfig badConfig2 = new ServerFlowConfig().setIntervalMs(1000).setSampleCount(3);
|
||||
ServerFlowConfig badConfig3 = new ServerFlowConfig().setIntervalMs(1000).setSampleCount(0);
|
||||
assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig1));
|
||||
assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig2));
|
||||
assertFalse(ClusterServerConfigManager.isValidFlowConfig(badConfig3));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.alibaba.csp.sentinel.cluster.server.connection;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class ConnectionGroupTest {
|
||||
|
||||
@Test
|
||||
public void testAddAndRemoveConnection() {
|
||||
String namespace = "test-conn-group";
|
||||
ConnectionGroup group = new ConnectionGroup(namespace);
|
||||
assertEquals(0, group.getConnectedCount());
|
||||
|
||||
String address1 = "12.23.34.45:5566";
|
||||
String address2 = "192.168.0.22:32123";
|
||||
String address3 = "12.23.34.45:5566";
|
||||
group.addConnection(address1);
|
||||
assertEquals(1, group.getConnectedCount());
|
||||
group.addConnection(address2);
|
||||
assertEquals(2, group.getConnectedCount());
|
||||
group.addConnection(address3);
|
||||
assertEquals(2, group.getConnectedCount());
|
||||
|
||||
group.removeConnection(address1);
|
||||
assertEquals(1, group.getConnectedCount());
|
||||
|
||||
group.removeConnection(address3);
|
||||
assertEquals(1, group.getConnectedCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.alibaba.csp.sentinel.cluster.server.connection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class ConnectionManagerTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ConnectionManager.clear();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
ConnectionManager.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAndConnectionAndGetConnectedCount() {
|
||||
String namespace = "test-namespace";
|
||||
assertEquals(0, ConnectionManager.getConnectedCount(namespace));
|
||||
|
||||
// Put one connection.
|
||||
ConnectionManager.addConnection(namespace, "12.23.34.45:1997");
|
||||
assertEquals(1, ConnectionManager.getConnectedCount(namespace));
|
||||
// Put duplicate connection.
|
||||
ConnectionManager.addConnection(namespace, "12.23.34.45:1997");
|
||||
assertEquals(1, ConnectionManager.getConnectedCount(namespace));
|
||||
|
||||
// Put another connection.
|
||||
ConnectionManager.addConnection(namespace, "12.23.34.49:22123");
|
||||
assertEquals(2, ConnectionManager.getConnectedCount(namespace));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetOrCreateGroupBadNamespace() {
|
||||
ConnectionManager.getOrCreateGroup("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrCreateGroupMultipleThread() throws Exception {
|
||||
final String namespace = "test-namespace";
|
||||
int threadCount = 32;
|
||||
final List<ConnectionGroup> groups = new CopyOnWriteArrayList<>();
|
||||
final CountDownLatch latch = new CountDownLatch(threadCount);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
groups.add(ConnectionManager.getOrCreateGroup(namespace));
|
||||
latch.countDown();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
latch.await();
|
||||
for (int i = 1; i < groups.size(); i++) {
|
||||
assertSame(groups.get(i - 1).getNamespace(), groups.get(i).getNamespace());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveConnection() {
|
||||
String namespace = "test-namespace-remove";
|
||||
String address1 = "12.23.34.45:1997";
|
||||
String address2 = "12.23.34.46:1998";
|
||||
String address3 = "12.23.34.47:1999";
|
||||
ConnectionManager.addConnection(namespace, address1);
|
||||
ConnectionManager.addConnection(namespace, address2);
|
||||
ConnectionManager.addConnection(namespace, address3);
|
||||
|
||||
assertEquals(3, ConnectionManager.getConnectedCount(namespace));
|
||||
ConnectionManager.removeConnection(namespace, address3);
|
||||
assertEquals(2, ConnectionManager.getConnectedCount(namespace));
|
||||
assertFalse(ConnectionManager.getOrCreateConnectionGroup(namespace).getConnectionSet().contains(
|
||||
new ConnectionDescriptor().setAddress(address3)
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOrCreateConnectionGroup() {
|
||||
String namespace = "test-namespace";
|
||||
assertNull(ConnectionManager.getConnectionGroup(namespace));
|
||||
ConnectionGroup group1 = ConnectionManager.getOrCreateConnectionGroup(namespace);
|
||||
assertNotNull(group1);
|
||||
|
||||
// Put one connection.
|
||||
ConnectionManager.addConnection(namespace, "12.23.34.45:1997");
|
||||
ConnectionGroup group2 = ConnectionManager.getOrCreateConnectionGroup(namespace);
|
||||
|
||||
assertSame(group1, group2);
|
||||
}
|
||||
}
|
||||
@@ -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.test;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
/**
|
||||
* Mock support for {@link TimeUtil}.
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({TimeUtil.class})
|
||||
public abstract class AbstractTimeBasedTest {
|
||||
|
||||
private long currentMillis = 0;
|
||||
|
||||
{
|
||||
PowerMockito.mockStatic(TimeUtil.class);
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void useActualTime() {
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenCallRealMethod();
|
||||
}
|
||||
|
||||
protected final void setCurrentMillis(long cur) {
|
||||
currentMillis = cur;
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void sleep(int t) {
|
||||
currentMillis += t;
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void sleepSecond(int timeSec) {
|
||||
sleep(timeSec * 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.api.mockito.PowerMockito;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
/**
|
||||
* Mock support for {@link TimeUtil}
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({TimeUtil.class})
|
||||
public abstract class AbstractTimeBasedTest {
|
||||
|
||||
private long currentMillis = 0;
|
||||
|
||||
{
|
||||
PowerMockito.mockStatic(TimeUtil.class);
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void useActualTime() {
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenCallRealMethod();
|
||||
}
|
||||
|
||||
protected final void setCurrentMillis(long cur) {
|
||||
currentMillis = cur;
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void sleep(int t) {
|
||||
currentMillis += t;
|
||||
PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis);
|
||||
}
|
||||
|
||||
protected final void sleepSecond(int timeSec) {
|
||||
sleep(timeSec * 1000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user