Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -0,0 +1,193 @@ | |||
/* | |||
* 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.statistic.ClusterMetricStatistics; | |||
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.slots.block.ClusterRuleConstant; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class ClusterFlowChecker { | |||
static TokenResult tryAcquireOrBorrowFromRefResource(FlowRule rule, int acquireCount, boolean prioritized) { | |||
// 1. First try acquire its own count. | |||
// TokenResult ownResult = acquireClusterToken(rule, acquireCount, prioritized); | |||
ClusterMetric metric = ClusterMetricStatistics.getMetric(rule.getClusterConfig().getFlowId()); | |||
if (metric == null) { | |||
return new TokenResult(TokenResultStatus.FAIL); | |||
} | |||
double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST); | |||
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.exceedCount; | |||
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); | |||
} | |||
if (prioritized) { | |||
double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING); | |||
if (occupyAvg <= ClusterServerConfigManager.maxOccupyRatio * globalThreshold) { | |||
int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold); | |||
if (waitInMs > 0) { | |||
return new TokenResult(TokenResultStatus.SHOULD_WAIT) | |||
.setRemaining(0) | |||
.setWaitInMs(waitInMs); | |||
} | |||
// Or else occupy failed, should be blocked. | |||
} | |||
} | |||
// 2. If failed, try to borrow from reference resource. | |||
// Assume it's valid as checked before. | |||
if (!ClusterServerConfigManager.borrowRefEnabled) { | |||
return new TokenResult(TokenResultStatus.NOT_AVAILABLE); | |||
} | |||
Long refFlowId = rule.getClusterConfig().getRefFlowId(); | |||
FlowRule refFlowRule = ClusterFlowRuleManager.getFlowRuleById(refFlowId); | |||
if (refFlowRule == null) { | |||
return new TokenResult(TokenResultStatus.NO_REF_RULE_EXISTS); | |||
} | |||
// TODO: check here | |||
ClusterMetric refMetric = ClusterMetricStatistics.getMetric(refFlowId); | |||
if (refMetric == null) { | |||
return new TokenResult(TokenResultStatus.FAIL); | |||
} | |||
double refOrders = refMetric.getAvg(ClusterFlowEvent.PASS); | |||
double refQps = refMetric.getAvg(ClusterFlowEvent.PASS_REQUEST); | |||
double splitRatio = refQps > 0 ? refOrders / refQps : 1; | |||
double selfGlobalThreshold = ClusterServerConfigManager.exceedCount * calcGlobalThreshold(rule); | |||
double refGlobalThreshold = ClusterServerConfigManager.exceedCount * calcGlobalThreshold(refFlowRule); | |||
long currentTime = TimeUtil.currentTimeMillis(); | |||
long latestRefTime = 0 /*refFlowRule.clusterQps.getStableWindowStartTime()*/; | |||
int sampleCount = 10; | |||
if (currentTime > latestRefTime | |||
&& (refOrders / refGlobalThreshold + 1.0d / sampleCount >= ((double)(currentTime - latestRefTime)) / 1000) | |||
|| refOrders == refGlobalThreshold) { | |||
return blockedResult(); | |||
} | |||
// double latestQps = metric.getAvg(ClusterFlowEvent.PASS); | |||
double refRatio = rule.getClusterConfig().getRefRatio(); | |||
if (refOrders / splitRatio + (acquireCount + latestQps) * refRatio | |||
<= refGlobalThreshold / splitRatio + selfGlobalThreshold * refRatio) { | |||
metric.add(ClusterFlowEvent.PASS, acquireCount); | |||
metric.add(ClusterFlowEvent.PASS_REQUEST, 1); | |||
return new TokenResult(TokenResultStatus.OK); | |||
} | |||
// TODO: log here? | |||
metric.add(ClusterFlowEvent.BLOCK, acquireCount); | |||
return blockedResult(); | |||
} | |||
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: | |||
// TODO: get real connected count grouped. | |||
int connectedCount = 1; | |||
return count * connectedCount; | |||
} | |||
} | |||
static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) { | |||
ClusterMetric metric = ClusterMetricStatistics.getMetric(rule.getClusterConfig().getFlowId()); | |||
if (metric == null) { | |||
return new TokenResult(TokenResultStatus.FAIL); | |||
} | |||
double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST); | |||
double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.exceedCount; | |||
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) { | |||
double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING); | |||
if (occupyAvg <= ClusterServerConfigManager.maxOccupyRatio * globalThreshold) { | |||
int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold); | |||
if (waitInMs > 0) { | |||
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); | |||
if (prioritized) { | |||
// Add prioritized block. | |||
metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount); | |||
} | |||
return blockedResult(); | |||
} | |||
} | |||
private static TokenResult blockedResult() { | |||
return new TokenResult(TokenResultStatus.BLOCKED) | |||
.setRemaining(0) | |||
.setWaitInMs(0); | |||
} | |||
private ClusterFlowChecker() {} | |||
} |
@@ -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; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; | |||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; | |||
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.FlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class ClusterFlowRuleManager { | |||
private static final Map<Long, FlowRule> FLOW_RULES = new ConcurrentHashMap<>(); | |||
private static final PropertyListener<List<FlowRule>> PROPERTY_LISTENER = new FlowRulePropertyListener(); | |||
private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<>(); | |||
static { | |||
currentProperty.addListener(PROPERTY_LISTENER); | |||
} | |||
/** | |||
* Listen to the {@link SentinelProperty} for {@link FlowRule}s. | |||
* The property is the source of cluster {@link FlowRule}s. | |||
* | |||
* @param property the property to listen. | |||
*/ | |||
public static void register2Property(SentinelProperty<List<FlowRule>> property) { | |||
synchronized (PROPERTY_LISTENER) { | |||
RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager"); | |||
currentProperty.removeListener(PROPERTY_LISTENER); | |||
property.addListener(PROPERTY_LISTENER); | |||
currentProperty = property; | |||
} | |||
} | |||
public static FlowRule getFlowRuleById(Long id) { | |||
if (!ClusterRuleUtil.validId(id)) { | |||
return null; | |||
} | |||
return FLOW_RULES.get(id); | |||
} | |||
private static Map<Long, FlowRule> buildClusterFlowRuleMap(List<FlowRule> list) { | |||
Map<Long, FlowRule> ruleMap = new ConcurrentHashMap<>(); | |||
if (list == null || list.isEmpty()) { | |||
return ruleMap; | |||
} | |||
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. | |||
Long flowId = rule.getClusterConfig().getFlowId(); | |||
if (flowId == null) { | |||
continue; | |||
} | |||
ruleMap.put(flowId, rule); | |||
// Prepare cluster metric from valid flow ID. | |||
ClusterMetricStatistics.putMetricIfAbsent(flowId, new ClusterMetric(100, 1)); | |||
} | |||
// Cleanup unused cluster metrics. | |||
Set<Long> previousSet = FLOW_RULES.keySet(); | |||
for (Long id : previousSet) { | |||
if (!ruleMap.containsKey(id)) { | |||
ClusterMetricStatistics.removeMetric(id); | |||
} | |||
} | |||
return ruleMap; | |||
} | |||
private static final class FlowRulePropertyListener implements PropertyListener<List<FlowRule>> { | |||
@Override | |||
public void configUpdate(List<FlowRule> conf) { | |||
Map<Long, FlowRule> rules = buildClusterFlowRuleMap(conf); | |||
if (rules != null) { | |||
FLOW_RULES.clear(); | |||
FLOW_RULES.putAll(rules); | |||
} | |||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received: " + FLOW_RULES); | |||
} | |||
@Override | |||
public void configLoad(List<FlowRule> conf) { | |||
Map<Long, FlowRule> rules = buildClusterFlowRuleMap(conf); | |||
if (rules != null) { | |||
FLOW_RULES.clear(); | |||
FLOW_RULES.putAll(rules); | |||
} | |||
RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded: " + FLOW_RULES); | |||
} | |||
} | |||
private ClusterFlowRuleManager() {} | |||
} |
@@ -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; | |||
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.statistic.ClusterParamMetricStatistics; | |||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; | |||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
public final class ClusterParamFlowChecker { | |||
static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection<Object> values) { | |||
ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(rule.getClusterConfig().getFlowId()); | |||
if (metric == null) { | |||
// Unexpected state, return FAIL. | |||
return new TokenResult(TokenResultStatus.FAIL); | |||
} | |||
boolean hasPassed = true; | |||
Object blockObject = null; | |||
for (Object value : values) { | |||
// TODO: origin is int * int, but current double! | |||
double curCount = metric.getAvg(value); | |||
double threshold = calcGlobalThreshold(rule); | |||
if (++curCount > threshold) { | |||
hasPassed = false; | |||
blockObject = value; | |||
break; | |||
} | |||
} | |||
if (hasPassed) { | |||
for (Object value : values) { | |||
metric.addValue(value, count); | |||
} | |||
} else { | |||
// TODO: log <blocked object> here? | |||
} | |||
return hasPassed ? newRawResponse(TokenResultStatus.OK): newRawResponse(TokenResultStatus.BLOCKED); | |||
} | |||
private static TokenResult newRawResponse(int status) { | |||
return new TokenResult(status) | |||
.setRemaining(0) | |||
.setWaitInMs(0); | |||
} | |||
private static double calcGlobalThreshold(ParamFlowRule 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 = 1; // TODO: get real connected count grouped. | |||
return count * connectedCount; | |||
} | |||
} | |||
private ClusterParamFlowChecker() {} | |||
} |
@@ -0,0 +1,138 @@ | |||
/* | |||
* 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.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.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.ParamFlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class ClusterParamFlowRuleManager { | |||
private static final Map<Long, ParamFlowRule> PARAM_RULES = new ConcurrentHashMap<>(); | |||
private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener(); | |||
private static SentinelProperty<List<ParamFlowRule>> currentProperty | |||
= new DynamicSentinelProperty<List<ParamFlowRule>>(); | |||
static { | |||
currentProperty.addListener(PROPERTY_LISTENER); | |||
} | |||
/** | |||
* Listen to the {@link SentinelProperty} for {@link ParamFlowRule}s. | |||
* The property is the source of {@link ParamFlowRule}s. | |||
* | |||
* @param property the property to listen | |||
*/ | |||
public static void register2Property(SentinelProperty<List<ParamFlowRule>> property) { | |||
synchronized (PROPERTY_LISTENER) { | |||
currentProperty.removeListener(PROPERTY_LISTENER); | |||
property.addListener(PROPERTY_LISTENER); | |||
currentProperty = property; | |||
RecordLog.info("[ClusterParamFlowRuleManager] New property has been registered to cluster param rule manager"); | |||
} | |||
} | |||
public static ParamFlowRule getParamFlowRuleById(Long id) { | |||
if (!ClusterRuleUtil.validId(id)) { | |||
return null; | |||
} | |||
return PARAM_RULES.get(id); | |||
} | |||
static class RulePropertyListener implements PropertyListener<List<ParamFlowRule>> { | |||
@Override | |||
public void configUpdate(List<ParamFlowRule> conf) { | |||
Map<Long, ParamFlowRule> rules = buildClusterRuleMap(conf); | |||
if (rules != null) { | |||
PARAM_RULES.clear(); | |||
PARAM_RULES.putAll(rules); | |||
} | |||
RecordLog.info("[ClusterFlowRuleManager] Cluster param flow rules received: " + PARAM_RULES); | |||
} | |||
@Override | |||
public void configLoad(List<ParamFlowRule> conf) { | |||
Map<Long, ParamFlowRule> rules = buildClusterRuleMap(conf); | |||
if (rules != null) { | |||
PARAM_RULES.clear(); | |||
PARAM_RULES.putAll(rules); | |||
} | |||
RecordLog.info("[ClusterFlowRuleManager] Cluster param flow rules received: " + PARAM_RULES); | |||
} | |||
} | |||
private static Map<Long, ParamFlowRule> buildClusterRuleMap(List<ParamFlowRule> list) { | |||
Map<Long, ParamFlowRule> ruleMap = new ConcurrentHashMap<>(); | |||
if (list == null || list.isEmpty()) { | |||
return ruleMap; | |||
} | |||
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); | |||
} | |||
// Flow id should not be null after filtered. | |||
Long flowId = rule.getClusterConfig().getFlowId(); | |||
if (flowId == null) { | |||
continue; | |||
} | |||
ruleMap.put(flowId, rule); | |||
// Prepare cluster metric from valid flow ID. | |||
ClusterParamMetricStatistics.putMetricIfAbsent(flowId, new ClusterParamMetric(100, 1)); | |||
} | |||
// Cleanup unused cluster metrics. | |||
Set<Long> previousSet = PARAM_RULES.keySet(); | |||
for (Long id : previousSet) { | |||
if (!ruleMap.containsKey(id)) { | |||
ClusterParamMetricStatistics.removeMetric(id); | |||
} | |||
} | |||
return ruleMap; | |||
} | |||
private ClusterParamFlowRuleManager() {} | |||
} |
@@ -0,0 +1,77 @@ | |||
/* | |||
* 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.TokenResultStatus; | |||
import com.alibaba.csp.sentinel.cluster.TokenResult; | |||
import com.alibaba.csp.sentinel.cluster.TokenService; | |||
import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||
/** | |||
* Default implementation for cluster {@link TokenService}. | |||
* | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
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); | |||
} | |||
if (isUsingReference(rule)) { | |||
return ClusterFlowChecker.tryAcquireOrBorrowFromRefResource(rule, acquireCount, prioritized); | |||
} | |||
return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized); | |||
} | |||
private boolean isUsingReference(FlowRule rule) { | |||
return rule.getClusterConfig().getStrategy() == ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF; | |||
} | |||
@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.getParamFlowRuleById(ruleId); | |||
if (rule == null) { | |||
return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); | |||
} | |||
return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params); | |||
} | |||
private boolean notValidRequest(Long id, int count) { | |||
return id == null || id <= 0 || count <= 0; | |||
} | |||
private TokenResult badRequest() { | |||
return new TokenResult(TokenResultStatus.BAD_REQUEST); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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.concurrent.ConcurrentHashMap; | |||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; | |||
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); | |||
} | |||
private ClusterMetricStatistics() {} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
* 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.concurrent.ConcurrentHashMap; | |||
import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; | |||
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); | |||
} | |||
private ClusterParamMetricStatistics() {} | |||
} |
@@ -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.flow.statistic.data; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
public enum ClusterFlowEvent { | |||
/** | |||
* Normal pass. | |||
*/ | |||
PASS, | |||
/** | |||
* Normal block. | |||
*/ | |||
BLOCK, | |||
/** | |||
* Token request (from client) passed. | |||
*/ | |||
PASS_REQUEST, | |||
/** | |||
* Token request (from client) blocked. | |||
*/ | |||
BLOCK_REQUEST, | |||
OCCUPIED_PASS, | |||
OCCUPIED_BLOCK, | |||
WAITING | |||
} |
@@ -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.data; | |||
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
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,76 @@ | |||
/* | |||
* 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; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterMetric { | |||
private final ClusterMetricLeapArray metric; | |||
public ClusterMetric(int windowLengthInMs, int intervalInSec) { | |||
this.metric = new ClusterMetricLeapArray(windowLengthInMs, intervalInSec); | |||
} | |||
public void add(ClusterFlowEvent event, long count) { | |||
metric.currentWindow().value().add(event, count); | |||
} | |||
public long getCurrentCount(ClusterFlowEvent event) { | |||
return metric.currentWindow().value().get(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; | |||
} | |||
public double getAvg(ClusterFlowEvent event) { | |||
return getSum(event) / metric.getIntervalInSecond(); | |||
} | |||
/** | |||
* | |||
* @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) { | |||
// TODO | |||
return metric.getOccupiedCount(event) + latestQps + acquireCount /*- xxx*/ <= threshold; | |||
} | |||
} |
@@ -0,0 +1,87 @@ | |||
/* | |||
* 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.flow.statistic.data.ClusterMetricBucket; | |||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; | |||
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; | |||
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; | |||
/** | |||
* The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs. | |||
* | |||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | |||
* @param intervalInSec the total time span of this {@link LeapArray} in seconds. | |||
*/ | |||
public ClusterMetricLeapArray(int windowLengthInMs, int intervalInSec) { | |||
super(windowLengthInMs, intervalInSec); | |||
ClusterFlowEvent[] events = ClusterFlowEvent.values(); | |||
this.occupyCounter = new LongAdder[events.length]; | |||
for (ClusterFlowEvent event : events) { | |||
occupyCounter[event.ordinal()] = new LongAdder(); | |||
} | |||
} | |||
@Override | |||
public ClusterMetricBucket newEmptyBucket() { | |||
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(); | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
/* | |||
* 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.slots.statistic.base.LongAdder; | |||
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
public class ClusterParamMetric { | |||
private final ClusterParameterLeapArray<LongAdder> metric; | |||
public ClusterParamMetric(int windowLengthInMs, int intervalInSec) { | |||
this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec); | |||
} | |||
public ClusterParamMetric(int windowLengthInMs, int intervalInSec, int maxCapacity) { | |||
this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec, 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) { | |||
sum += getCount(bucket.get(value)); | |||
} | |||
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(); | |||
} | |||
} |
@@ -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.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; | |||
/** | |||
* @author Eric Zhao | |||
* @param <C> counter type | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>> { | |||
private final int maxCapacity; | |||
public ClusterParameterLeapArray(int windowLengthInMs, int intervalInSec) { | |||
this(windowLengthInMs, intervalInSec, DEFAULT_CLUSTER_MAX_CAPACITY); | |||
} | |||
public ClusterParameterLeapArray(int windowLengthInMs, int intervalInSec, int maxCapacity) { | |||
super(windowLengthInMs, intervalInSec); | |||
AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive"); | |||
this.maxCapacity = maxCapacity; | |||
} | |||
@Override | |||
public CacheMap<Object, C> newEmptyBucket() { | |||
return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); | |||
} | |||
@Override | |||
protected WindowWrap<CacheMap<Object, C>> resetWindowTo(WindowWrap<CacheMap<Object, C>> w, | |||
long startTime) { | |||
w.value().clear(); | |||
return w; | |||
} | |||
public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000; | |||
} |
@@ -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; | |||
/** | |||
* Token server interface for distributed flow control. | |||
* | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public interface ClusterTokenServer { | |||
void start() throws Exception; | |||
void stop() throws Exception; | |||
} |
@@ -0,0 +1,175 @@ | |||
/* | |||
* 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 | |||
*/ | |||
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 final int port = 11111; | |||
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); | |||
@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(Integer.valueOf(port)).addListener(new GenericFutureListener<ChannelFuture>() { | |||
@Override | |||
public void operationComplete(ChannelFuture future) { | |||
if (future.cause() != null) { | |||
RecordLog.info("Token server start failed", future.cause()); | |||
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF); | |||
//try { | |||
// Thread.sleep((failStartTimes.get() + 1) * 1000); | |||
// start(); | |||
//} catch (Throwable e) { | |||
// RecordLog.info("Fail to start token server:", e); | |||
//} | |||
} else { | |||
RecordLog.info("Token server start success"); | |||
currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED); | |||
//failStartTimes.set(0); | |||
} | |||
} | |||
}); | |||
} | |||
@Override | |||
public void stop() { | |||
// If still initializing, wait for ready. | |||
while (currentState.get() == SERVER_STATUS_STARTING) { | |||
try { | |||
Thread.sleep(1000); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) { | |||
try { | |||
bossGroup.shutdownGracefully(); | |||
workerGroup.shutdownGracefully(); | |||
connectionPool.shutdownAll(); | |||
failedTimes.set(0); | |||
RecordLog.info("Token server stopped"); | |||
} catch (Exception ex) { | |||
RecordLog.warn("Failed to stop token server", 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,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,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; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.ServiceLoader; | |||
import com.alibaba.csp.sentinel.cluster.TokenService; | |||
import com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class TokenServiceProvider { | |||
private static TokenService service = null; | |||
private static final ServiceLoader<TokenService> LOADER = ServiceLoader.load(TokenService.class); | |||
static { | |||
resolveTokenServiceSpi(); | |||
} | |||
public static TokenService getService() { | |||
return service; | |||
} | |||
private static void resolveTokenServiceSpi() { | |||
boolean hasOther = false; | |||
List<TokenService> list = new ArrayList<TokenService>(); | |||
for (TokenService service : LOADER) { | |||
if (service.getClass() != DefaultTokenService.class) { | |||
hasOther = true; | |||
list.add(service); | |||
} | |||
} | |||
if (hasOther) { | |||
service = list.get(0); | |||
} else { | |||
// No custom token service, using default. | |||
service = new DefaultTokenService(); | |||
} | |||
RecordLog.info("[TokenServiceProvider] Global token service resolved: " | |||
+ service.getClass().getCanonicalName()); | |||
} | |||
} |
@@ -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.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: {0}", type); | |||
return null; | |||
} | |||
Object data; | |||
if (source.readableBytes() == 0) { | |||
data = null; | |||
} else { | |||
// TODO: handle decode error here. | |||
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 <{0}>", 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,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.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.util.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.loadFirstInstance(ResponseEntityWriter.class); | |||
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.loadFirstInstance(RequestEntityDecoder.class); | |||
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,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.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 (4) | 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; | |||
} | |||
// TODO: handle null here. | |||
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 Eric Zhao | |||
*/ | |||
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) { | |||
// TODO: should check rules exist here? | |||
List<Object> params = new ArrayList<>(amount); | |||
for (int i = 0; i < amount; i++) { | |||
decodeParam(source, params); | |||
} | |||
requestData.setParams(params); | |||
return requestData; | |||
} | |||
} | |||
// TODO: handle null here. | |||
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,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.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) { | |||
// TODO: may need to throw exception? | |||
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,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.config; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
public final class ClusterServerConfigManager { | |||
private static final int DEFAULT_PORT = 8730; | |||
private static final int DEFAULT_IDLE_SECONDS = 600; | |||
public static volatile int port = DEFAULT_PORT; | |||
public static volatile double exceedCount = 1.0d; | |||
public static volatile boolean borrowRefEnabled = true; | |||
public static volatile int idleSeconds = DEFAULT_IDLE_SECONDS; | |||
public static volatile double maxOccupyRatio = 1.0d; | |||
// TODO: implement here. | |||
private ClusterServerConfigManager() {} | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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 | |||
*/ | |||
public interface Connection extends AutoCloseable { | |||
SocketAddress getLocalAddress(); | |||
int getRemotePort(); | |||
String getRemoteIP(); | |||
void refreshLastReadTime(long lastReadTime); | |||
long getLastReadTime(); | |||
String getConnectionKey(); | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* 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.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; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ConnectionGroup { | |||
private String namespace; | |||
private Set<String> addressSet = new ConcurrentSkipListSet<>(); | |||
private Set<String> hostSet = new ConcurrentSkipListSet<>(); | |||
private 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"); | |||
addressSet.add(address); | |||
String[] ip = address.split(":"); | |||
if (ip != null && ip.length >= 1) { | |||
hostSet.add(ip[0]); | |||
} | |||
connectedCount.incrementAndGet(); | |||
return this; | |||
} | |||
public ConnectionGroup removeConnection(String address) { | |||
AssertUtil.notEmpty(address, "address cannot be empty"); | |||
addressSet.remove(address); | |||
String[] ip = address.split(":"); | |||
if (ip != null && ip.length >= 1) { | |||
hostSet.remove(ip[0]); | |||
} | |||
connectedCount.decrementAndGet(); | |||
return this; | |||
} | |||
public String getNamespace() { | |||
return namespace; | |||
} | |||
public Set<String> getAddressSet() { | |||
return addressSet; | |||
} | |||
public Set<String> getHostSet() { | |||
return hostSet; | |||
} | |||
public int getConnectedCount() { | |||
return connectedCount.get(); | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* 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; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class ConnectionManager { | |||
private static final Map<String, ConnectionGroup> CONN_MAP = new ConcurrentHashMap<>(); | |||
private ConnectionManager() {} | |||
} |
@@ -0,0 +1,149 @@ | |||
/* | |||
* 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 { | |||
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; | |||
/** | |||
* 创建一个connection,并放入连接池中 | |||
* | |||
* @param channel | |||
*/ | |||
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; | |||
} | |||
/** | |||
* 刷新一个连接上的最新read时间 | |||
* | |||
* @param channel | |||
*/ | |||
public void refreshLastReadTime(Channel channel) { | |||
if (channel != null) { | |||
String connKey = getConnectionKey(channel); | |||
Connection connection = CONNECTION_MAP.get(connKey); | |||
//不应该为null,需要处理这种情况吗? | |||
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,85 @@ | |||
/* | |||
* 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 | |||
*/ | |||
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.log.RecordLog; | |||
/** | |||
* @author xuyue | |||
* @author Eric Zhao | |||
*/ | |||
public class ScanIdleConnectionTask implements Runnable { | |||
private ConnectionPool connectionPool; | |||
public ScanIdleConnectionTask(ConnectionPool connectionPool) { | |||
this.connectionPool = connectionPool; | |||
} | |||
@Override | |||
public void run() { | |||
try { | |||
int idleSeconds = ClusterServerConfigManager.idleSeconds; | |||
long idleTime = idleSeconds * 1000; | |||
if (idleTime < 0) { | |||
idleTime = 600 * 1000; | |||
} | |||
long now = System.currentTimeMillis(); | |||
List<Connection> connections = connectionPool.listAllConnection(); | |||
for (Connection conn : connections) { | |||
if ((now - conn.getLastReadTime()) > idleTime) { | |||
RecordLog.info( | |||
String.format("[ScanIdleConnectionTask] The connection <%s:%d> has been idle for <%d>s. " | |||
+ "It will be closed now.", conn.getRemoteIP(), conn.getRemotePort(), idleSeconds) | |||
); | |||
conn.close(); | |||
} | |||
} | |||
} catch (Throwable t) { | |||
// TODO: should log here. | |||
} | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
/* | |||
* 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 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.response.data.FlowTokenResponseData; | |||
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.RequestProcessorRegistry; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
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 connectionPool; | |||
public TokenServerHandler(ConnectionPool connectionPool) { | |||
this.connectionPool = connectionPool; | |||
} | |||
@Override | |||
public void channelActive(ChannelHandlerContext ctx) throws Exception { | |||
System.out.println("[TokenServerHandler] Connection established"); | |||
super.channelActive(ctx); | |||
} | |||
@Override | |||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { | |||
System.out.println("[TokenServerHandler] Connection inactive"); | |||
super.channelInactive(ctx); | |||
} | |||
@Override | |||
@SuppressWarnings("unchecked") | |||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | |||
connectionPool.refreshLastReadTime(ctx.channel()); | |||
System.out.println(String.format("[%s] Server message recv: %s", System.currentTimeMillis(), msg)); | |||
if (msg instanceof ClusterRequest) { | |||
ClusterRequest request = (ClusterRequest)msg; | |||
RequestProcessor<?, ?> processor = RequestProcessorRegistry.getProcessor(request.getType()); | |||
if (processor == null) { | |||
System.out.println("[TokenServerHandler] No processor for request type: " + request.getType()); | |||
writeNoProcessorResponse(ctx, request); | |||
} else { | |||
ClusterResponse<?> response = processor.processRequest(request); | |||
writeResponse(ctx, response); | |||
} | |||
} | |||
} | |||
private void writeNoProcessorResponse(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); | |||
} | |||
} |
@@ -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.server.processor; | |||
import com.alibaba.csp.sentinel.cluster.TokenResult; | |||
import com.alibaba.csp.sentinel.cluster.TokenService; | |||
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 | |||
*/ | |||
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,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.processor; | |||
import java.util.Collection; | |||
import com.alibaba.csp.sentinel.cluster.TokenResult; | |||
import com.alibaba.csp.sentinel.cluster.TokenService; | |||
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 | |||
*/ | |||
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,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.processor; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class RequestProcessorRegistry { | |||
private static final Map<Integer, RequestProcessor> PROCESSOR_MAP = new ConcurrentHashMap<>(); | |||
public static RequestProcessor getProcessor(int type) { | |||
return PROCESSOR_MAP.get(type); | |||
} | |||
public static void addProcessorIfAbsent(int type, RequestProcessor processor) { | |||
// TBD: use putIfAbsent in JDK 1.8. | |||
if (PROCESSOR_MAP.containsKey(type)) { | |||
return; | |||
} | |||
PROCESSOR_MAP.put(type, processor); | |||
} | |||
public static void addProcessor(int type, RequestProcessor processor) { | |||
AssertUtil.notNull(processor, "processor cannot be null"); | |||
PROCESSOR_MAP.put(type, processor); | |||
} | |||
private RequestProcessorRegistry() {} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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 | |||
*/ | |||
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 |