Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -1,4 +1,5 @@ | |||||
ignore: | ignore: | ||||
- "sentinel-demo/.*" | - "sentinel-demo/.*" | ||||
- "sentinel-dashboard/.*" | - "sentinel-dashboard/.*" | ||||
- "sentinel-benchmark/.*" | |||||
- "sentinel-benchmark/.*" | |||||
- "sentinel-transport/.*" |
@@ -0,0 +1,7 @@ | |||||
# Sentinel Cluster Flow Control | |||||
This is the default implementation of Sentinel cluster flow control. | |||||
- `sentinel-cluster-common-default`: common module for cluster transport and functions | |||||
- `sentinel-cluster-client-default`: default cluster client module using Netty as underlying transport library | |||||
- `sentinel-cluster-server-default`: default cluster server module |
@@ -55,7 +55,6 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { | |||||
changeServer(clusterClientConfig); | changeServer(clusterClientConfig); | ||||
} | } | ||||
}); | }); | ||||
// TODO: check here, who should start the client? | |||||
initNewConnection(); | initNewConnection(); | ||||
} | } | ||||
@@ -90,7 +89,6 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { | |||||
return; | return; | ||||
} | } | ||||
try { | try { | ||||
// TODO: what if the client is pending init? | |||||
if (transportClient != null) { | if (transportClient != null) { | ||||
transportClient.stop(); | transportClient.stop(); | ||||
} | } | ||||
@@ -108,12 +106,14 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { | |||||
if (shouldStart.get()) { | if (shouldStart.get()) { | ||||
if (transportClient != null) { | if (transportClient != null) { | ||||
transportClient.start(); | transportClient.start(); | ||||
} else { | |||||
RecordLog.warn("[DefaultClusterTokenClient] Cannot start transport client: client not created"); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
private void stopClientIfStarted() throws Exception { | private void stopClientIfStarted() throws Exception { | ||||
if (shouldStart.get()) { | |||||
if (shouldStart.compareAndSet(true, false)) { | |||||
if (transportClient != null) { | if (transportClient != null) { | ||||
transportClient.stop(); | transportClient.stop(); | ||||
} | } | ||||
@@ -129,9 +129,7 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { | |||||
@Override | @Override | ||||
public void stop() throws Exception { | public void stop() throws Exception { | ||||
if (shouldStart.compareAndSet(true, false)) { | |||||
stopClientIfStarted(); | |||||
} | |||||
stopClientIfStarted(); | |||||
} | } | ||||
@Override | @Override | ||||
@@ -104,6 +104,7 @@ public final class ClusterClientConfigManager { | |||||
public static boolean isValidConfig(ClusterClientConfig config) { | public static boolean isValidConfig(ClusterClientConfig config) { | ||||
return config != null && StringUtil.isNotBlank(config.getServerHost()) | return config != null && StringUtil.isNotBlank(config.getServerHost()) | ||||
&& config.getServerPort() > 0 | && config.getServerPort() > 0 | ||||
&& config.getServerPort() <= 65535 | |||||
&& config.getRequestTimeout() > 0; | && config.getRequestTimeout() > 0; | ||||
} | } | ||||
@@ -45,14 +45,13 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { | |||||
@Override | @Override | ||||
public void channelActive(ChannelHandlerContext ctx) throws Exception { | public void channelActive(ChannelHandlerContext ctx) throws Exception { | ||||
currentState.compareAndSet(ClientConstants.CLIENT_STATUS_PENDING, ClientConstants.CLIENT_STATUS_STARTED); | |||||
currentState.set(ClientConstants.CLIENT_STATUS_STARTED); | |||||
fireClientPing(ctx); | fireClientPing(ctx); | ||||
RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress()); | RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress()); | ||||
} | } | ||||
@Override | @Override | ||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | ||||
System.out.println(String.format("[%s] Client message recv: %s", System.currentTimeMillis(), msg)); // TODO: remove here | |||||
if (msg instanceof ClusterResponse) { | if (msg instanceof ClusterResponse) { | ||||
ClusterResponse<?> response = (ClusterResponse) msg; | ClusterResponse<?> response = (ClusterResponse) msg; | ||||
@@ -96,7 +95,7 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { | |||||
@Override | @Override | ||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { | ||||
RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: " + ctx.channel().remoteAddress()); | RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: " + ctx.channel().remoteAddress()); | ||||
currentState.compareAndSet(ClientConstants.CLIENT_STATUS_STARTED, ClientConstants.CLIENT_STATUS_OFF); | |||||
currentState.set(ClientConstants.CLIENT_STATUS_OFF); | |||||
disconnectCallback.run(); | disconnectCallback.run(); | ||||
} | } | ||||
@@ -46,7 +46,7 @@ public class ModifyClusterClientConfigHandler implements CommandHandler<String> | |||||
ClusterClientConfig clusterClientConfig = JSON.parseObject(data, ClusterClientConfig.class); | ClusterClientConfig clusterClientConfig = JSON.parseObject(data, ClusterClientConfig.class); | ||||
ClusterClientConfigManager.applyNewConfig(clusterClientConfig); | ClusterClientConfigManager.applyNewConfig(clusterClientConfig); | ||||
return CommandResponse.ofSuccess("ok"); | |||||
return CommandResponse.ofSuccess("success"); | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); | RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); | ||||
return CommandResponse.ofFailure(e, "decode client cluster config error"); | return CommandResponse.ofFailure(e, "decode client cluster config error"); | ||||
@@ -15,6 +15,7 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.cluster.flow.rule; | package com.alibaba.csp.sentinel.cluster.flow.rule; | ||||
import java.util.ArrayList; | |||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
@@ -163,6 +164,11 @@ public final class ClusterFlowRuleManager { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* Remove cluster flow rule property for a specific namespace. | |||||
* | |||||
* @param namespace valid namespace | |||||
*/ | |||||
public static void removeProperty(String namespace) { | public static void removeProperty(String namespace) { | ||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | ||||
synchronized (UPDATE_LOCK) { | synchronized (UPDATE_LOCK) { | ||||
@@ -189,6 +195,12 @@ public final class ClusterFlowRuleManager { | |||||
} | } | ||||
} | } | ||||
/** | |||||
* Get flow rule by rule ID. | |||||
* | |||||
* @param id rule ID | |||||
* @return flow rule | |||||
*/ | |||||
public static FlowRule getFlowRuleById(Long id) { | public static FlowRule getFlowRuleById(Long id) { | ||||
if (!ClusterRuleUtil.validId(id)) { | if (!ClusterRuleUtil.validId(id)) { | ||||
return null; | return null; | ||||
@@ -196,10 +208,57 @@ public final class ClusterFlowRuleManager { | |||||
return FLOW_RULES.get(id); | return FLOW_RULES.get(id); | ||||
} | } | ||||
public static List<FlowRule> getAllFlowRules() { | |||||
return new ArrayList<>(FLOW_RULES.values()); | |||||
} | |||||
/** | |||||
* Get all cluster flow rules within a specific namespace. | |||||
* | |||||
* @param namespace valid namespace | |||||
* @return cluster flow rules within the provided namespace | |||||
*/ | |||||
public static List<FlowRule> getFlowRules(String namespace) { | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return new ArrayList<>(); | |||||
} | |||||
List<FlowRule> rules = new ArrayList<>(); | |||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); | |||||
if (flowIdSet == null || flowIdSet.isEmpty()) { | |||||
return rules; | |||||
} | |||||
for (Long flowId : flowIdSet) { | |||||
FlowRule rule = FLOW_RULES.get(flowId); | |||||
if (rule != null) { | |||||
rules.add(rule); | |||||
} | |||||
} | |||||
return rules; | |||||
} | |||||
/** | |||||
* Load flow rules for a specific namespace. The former rules of the namespace will be replaced. | |||||
* | |||||
* @param namespace a valid namespace | |||||
* @param rules rule list | |||||
*/ | |||||
public static void loadRules(String namespace, List<FlowRule> rules) { | |||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | |||||
NamespaceFlowProperty<FlowRule> property = PROPERTY_MAP.get(namespace); | |||||
if (property != null) { | |||||
property.getProperty().updateValue(rules); | |||||
} | |||||
} | |||||
private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { | private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { | ||||
NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>()); | NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet<Long>()); | ||||
} | } | ||||
/** | |||||
* Clear all rules of the provided namespace and reset map. | |||||
* | |||||
* @param namespace valid namespace | |||||
*/ | |||||
private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { | private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { | ||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); | Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); | ||||
if (flowIdSet != null && !flowIdSet.isEmpty()) { | if (flowIdSet != null && !flowIdSet.isEmpty()) { | ||||
@@ -15,6 +15,7 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.cluster.flow.rule; | package com.alibaba.csp.sentinel.cluster.flow.rule; | ||||
import java.util.ArrayList; | |||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
@@ -217,6 +218,54 @@ public final class ClusterParamFlowRuleManager { | |||||
return PARAM_RULES.get(id); | return PARAM_RULES.get(id); | ||||
} | } | ||||
public static List<ParamFlowRule> getAllParamRules() { | |||||
return new ArrayList<>(PARAM_RULES.values()); | |||||
} | |||||
/** | |||||
* Get all cluster parameter flow rules within a specific namespace. | |||||
* | |||||
* @param namespace a valid namespace | |||||
* @return cluster parameter flow rules within the provided namespace | |||||
*/ | |||||
public static List<ParamFlowRule> getParamRules(String namespace) { | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return new ArrayList<>(); | |||||
} | |||||
List<ParamFlowRule> rules = new ArrayList<>(); | |||||
Set<Long> flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); | |||||
if (flowIdSet == null || flowIdSet.isEmpty()) { | |||||
return rules; | |||||
} | |||||
for (Long flowId : flowIdSet) { | |||||
ParamFlowRule rule = PARAM_RULES.get(flowId); | |||||
if (rule != null) { | |||||
rules.add(rule); | |||||
} | |||||
} | |||||
return rules; | |||||
} | |||||
/** | |||||
* Load parameter flow rules for a specific namespace. The former rules of the namespace will be replaced. | |||||
* | |||||
* @param namespace a valid namespace | |||||
* @param rules rule list | |||||
*/ | |||||
public static void loadRules(String namespace, List<ParamFlowRule> rules) { | |||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | |||||
NamespaceFlowProperty<ParamFlowRule> property = PROPERTY_MAP.get(namespace); | |||||
if (property != null) { | |||||
property.getProperty().updateValue(rules); | |||||
} | |||||
} | |||||
/** | |||||
* Get connected count for associated namespace of given {@code flowId}. | |||||
* | |||||
* @param flowId existing rule ID | |||||
* @return connected count | |||||
*/ | |||||
public static int getConnectedCount(long flowId) { | public static int getConnectedCount(long flowId) { | ||||
if (flowId <= 0) { | if (flowId <= 0) { | ||||
return 0; | return 0; | ||||
@@ -79,7 +79,7 @@ public class SentinelDefaultTokenServer implements ClusterTokenServer { | |||||
} | } | ||||
try { | try { | ||||
if (server != null) { | if (server != null) { | ||||
stopServerIfStarted(); | |||||
stopServer(); | |||||
} | } | ||||
this.server = new NettyTransportServer(newPort); | this.server = new NettyTransportServer(newPort); | ||||
this.port = newPort; | this.port = newPort; | ||||
@@ -101,13 +101,11 @@ public class SentinelDefaultTokenServer implements ClusterTokenServer { | |||||
} | } | ||||
} | } | ||||
private void stopServerIfStarted() throws Exception { | |||||
if (shouldStart.get()) { | |||||
if (server != null) { | |||||
server.stop(); | |||||
if (embedded) { | |||||
handleEmbeddedStop(); | |||||
} | |||||
private void stopServer() throws Exception { | |||||
if (server != null) { | |||||
server.stop(); | |||||
if (embedded) { | |||||
handleEmbeddedStop(); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -136,7 +134,7 @@ public class SentinelDefaultTokenServer implements ClusterTokenServer { | |||||
@Override | @Override | ||||
public void stop() throws Exception { | public void stop() throws Exception { | ||||
if (shouldStart.compareAndSet(true, false)) { | if (shouldStart.compareAndSet(true, false)) { | ||||
stopServerIfStarted(); | |||||
stopServer(); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,42 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSON; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/flowRules") | |||||
public class FetchClusterFlowRulesCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String namespace = request.getParam("namespace"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getAllFlowRules())); | |||||
} else { | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterFlowRuleManager.getFlowRules(namespace))); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSON; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/paramRules") | |||||
public class FetchClusterParamFlowRulesCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String namespace = request.getParam("namespace"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getAllParamRules())); | |||||
} else { | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(ClusterParamFlowRuleManager.getParamRules(namespace))); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSONObject; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/fetchConfig") | |||||
public class FetchClusterServerConfigHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String namespace = request.getParam("namespace"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return globalConfigResult(); | |||||
} | |||||
return namespaceConfigResult(namespace); | |||||
} | |||||
private CommandResponse<String> namespaceConfigResult(/*@NonEmpty*/ String namespace) { | |||||
ServerFlowConfig flowConfig = new ServerFlowConfig() | |||||
.setExceedCount(ClusterServerConfigManager.getExceedCount(namespace)) | |||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio(namespace)) | |||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs(namespace)) | |||||
.setSampleCount(ClusterServerConfigManager.getSampleCount(namespace)); | |||||
JSONObject config = new JSONObject() | |||||
.fluentPut("flow", flowConfig); | |||||
return CommandResponse.ofSuccess(config.toJSONString()); | |||||
} | |||||
private CommandResponse<String> globalConfigResult() { | |||||
ServerTransportConfig transportConfig = new ServerTransportConfig() | |||||
.setPort(ClusterServerConfigManager.getPort()) | |||||
.setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); | |||||
ServerFlowConfig flowConfig = new ServerFlowConfig() | |||||
.setExceedCount(ClusterServerConfigManager.getExceedCount()) | |||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) | |||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs()) | |||||
.setSampleCount(ClusterServerConfigManager.getSampleCount()); | |||||
JSONObject config = new JSONObject() | |||||
.fluentPut("transport", transportConfig) | |||||
.fluentPut("flow", flowConfig) | |||||
.fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet()); | |||||
return CommandResponse.ofSuccess(config.toJSONString()); | |||||
} | |||||
} | |||||
@@ -0,0 +1,69 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; | |||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionGroup; | |||||
import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.fastjson.JSONArray; | |||||
import com.alibaba.fastjson.JSONObject; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/info") | |||||
public class FetchClusterServerInfoCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
JSONObject info = new JSONObject(); | |||||
JSONArray connectionGroups = new JSONArray(); | |||||
Set<String> namespaceSet = ClusterServerConfigManager.getNamespaceSet(); | |||||
for (String namespace : namespaceSet) { | |||||
ConnectionGroup group = ConnectionManager.getConnectionGroup(namespace); | |||||
if (group != null) { | |||||
connectionGroups.add(group); | |||||
} | |||||
} | |||||
ServerTransportConfig transportConfig = new ServerTransportConfig() | |||||
.setPort(ClusterServerConfigManager.getPort()) | |||||
.setIdleSeconds(ClusterServerConfigManager.getIdleSeconds()); | |||||
ServerFlowConfig flowConfig = new ServerFlowConfig() | |||||
.setExceedCount(ClusterServerConfigManager.getExceedCount()) | |||||
.setMaxOccupyRatio(ClusterServerConfigManager.getMaxOccupyRatio()) | |||||
.setIntervalMs(ClusterServerConfigManager.getIntervalMs()) | |||||
.setSampleCount(ClusterServerConfigManager.getSampleCount()); | |||||
info.fluentPut("port", ClusterServerConfigManager.getPort()) | |||||
.fluentPut("connection", connectionGroups) | |||||
.fluentPut("transport", transportConfig) | |||||
.fluentPut("flow", flowConfig) | |||||
.fluentPut("namespaceSet", ClusterServerConfigManager.getNamespaceSet()); | |||||
return CommandResponse.ofSuccess(info.toJSONString()); | |||||
} | |||||
} | |||||
@@ -0,0 +1,63 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import java.net.URLDecoder; | |||||
import java.util.List; | |||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSONArray; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/modifyFlowRules") | |||||
public class ModifyClusterFlowRulesCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String namespace = request.getParam("namespace"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); | |||||
} | |||||
String data = request.getParam("data"); | |||||
if (StringUtil.isBlank(data)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); | |||||
} | |||||
try { | |||||
data = URLDecoder.decode(data, "UTF-8"); | |||||
RecordLog.info("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{0}>: {1}", namespace, data); | |||||
List<FlowRule> flowRules = JSONArray.parseArray(data, FlowRule.class); | |||||
ClusterFlowRuleManager.loadRules(namespace, flowRules); | |||||
return CommandResponse.ofSuccess(SUCCESS); | |||||
} catch (Exception e) { | |||||
RecordLog.warn("[ModifyClusterFlowRulesCommandHandler] Decode cluster flow rules error", e); | |||||
return CommandResponse.ofFailure(e, "decode cluster flow rules error"); | |||||
} | |||||
} | |||||
private static final String SUCCESS = "success"; | |||||
} |
@@ -0,0 +1,63 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import java.net.URLDecoder; | |||||
import java.util.List; | |||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSONArray; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/modifyParamRules") | |||||
public class ModifyClusterParamFlowRulesCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String namespace = request.getParam("namespace"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty namespace")); | |||||
} | |||||
String data = request.getParam("data"); | |||||
if (StringUtil.isBlank(data)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); | |||||
} | |||||
try { | |||||
data = URLDecoder.decode(data, "UTF-8"); | |||||
RecordLog.info("[ModifyClusterParamFlowRulesCommandHandler] Receiving cluster param rules for namespace <{0}>: {1}", namespace, data); | |||||
List<ParamFlowRule> flowRules = JSONArray.parseArray(data, ParamFlowRule.class); | |||||
ClusterParamFlowRuleManager.loadRules(namespace, flowRules); | |||||
return CommandResponse.ofSuccess(SUCCESS); | |||||
} catch (Exception e) { | |||||
RecordLog.warn("[ModifyClusterParamFlowRulesCommandHandler] Decode cluster param rules error", e); | |||||
return CommandResponse.ofFailure(e, "decode cluster param rules error"); | |||||
} | |||||
} | |||||
private static final String SUCCESS = "success"; | |||||
} |
@@ -0,0 +1,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.command.handler; | |||||
import java.net.URLDecoder; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerFlowConfig; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSON; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/modifyFlowConfig") | |||||
public class ModifyClusterServerFlowConfigHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String data = request.getParam("data"); | |||||
if (StringUtil.isBlank(data)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); | |||||
} | |||||
String namespace = request.getParam("namespace"); | |||||
try { | |||||
data = URLDecoder.decode(data, "utf-8"); | |||||
if (StringUtil.isEmpty(namespace)) { | |||||
RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: " + data); | |||||
ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); | |||||
ClusterServerConfigManager.loadGlobalFlowConfig(config); | |||||
} else { | |||||
RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{0}>: {1}", namespace, data); | |||||
ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); | |||||
ClusterServerConfigManager.loadFlowConfig(namespace, config); | |||||
} | |||||
return CommandResponse.ofSuccess("success"); | |||||
} catch (Exception e) { | |||||
RecordLog.warn("[ModifyClusterServerFlowConfigHandler] Decode cluster server flow config error", e); | |||||
return CommandResponse.ofFailure(e, "decode cluster server flow config error"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/modifyTransportConfig") | |||||
public class ModifyClusterServerTransportConfigHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String portValue = request.getParam("port"); | |||||
if (StringUtil.isBlank(portValue)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty port")); | |||||
} | |||||
String idleSecondsValue = request.getParam("idleSeconds"); | |||||
if (StringUtil.isBlank(idleSecondsValue)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid empty idleSeconds")); | |||||
} | |||||
try { | |||||
int port = Integer.valueOf(portValue); | |||||
int idleSeconds = Integer.valueOf(idleSecondsValue); | |||||
ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() | |||||
.setPort(port).setIdleSeconds(idleSeconds)); | |||||
return CommandResponse.ofSuccess("success"); | |||||
} catch (NumberFormatException e) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("invalid parameter")); | |||||
} catch (Exception ex) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("unexpected error")); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.cluster.server.command.handler; | |||||
import java.net.URLDecoder; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; | |||||
import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSON; | |||||
import com.alibaba.fastjson.TypeReference; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.4.0 | |||||
*/ | |||||
@CommandMapping(name = "cluster/server/modifyNamespaceSet") | |||||
public class ModifyServerNamespaceSetHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String data = request.getParam("data"); | |||||
if (StringUtil.isBlank(data)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("empty data")); | |||||
} | |||||
try { | |||||
data = URLDecoder.decode(data, "utf-8"); | |||||
RecordLog.info("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: " + data); | |||||
Set<String> set = JSON.parseObject(data, new TypeReference<Set<String>>() {}); | |||||
ClusterServerConfigManager.loadServerNamespaceSet(set); | |||||
return CommandResponse.ofSuccess("success"); | |||||
} catch (Exception e) { | |||||
RecordLog.warn("[ModifyServerNamespaceSetHandler] Decode cluster server namespace set error", e); | |||||
return CommandResponse.ofFailure(e, "decode client cluster config error"); | |||||
} | |||||
} | |||||
} |
@@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.cluster.server.config; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | import java.util.Set; | ||||
@@ -110,6 +111,35 @@ public final class ClusterServerConfigManager { | |||||
} | } | ||||
} | } | ||||
public static void loadServerNamespaceSet(Set<String> namespaceSet) { | |||||
namespaceSetProperty.updateValue(namespaceSet); | |||||
} | |||||
public static void loadGlobalTransportConfig(ServerTransportConfig config) { | |||||
transportConfigProperty.updateValue(config); | |||||
} | |||||
public static void loadGlobalFlowConfig(ServerFlowConfig config) { | |||||
globalFlowProperty.updateValue(config); | |||||
} | |||||
/** | |||||
* Load server flow config for a specific namespace. | |||||
* | |||||
* @param namespace a valid namespace | |||||
* @param config valid flow config for the namespace | |||||
*/ | |||||
public static void loadFlowConfig(String namespace, ServerFlowConfig config) { | |||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | |||||
// TODO: Support namespace-scope server flow config. | |||||
globalFlowProperty.updateValue(config); | |||||
} | |||||
public static void addTransportConfigChangeObserver(ServerTransportConfigObserver observer) { | |||||
AssertUtil.notNull(observer, "observer cannot be null"); | |||||
TRANSPORT_CONFIG_OBSERVERS.add(observer); | |||||
} | |||||
private static class ServerNamespaceSetPropertyListener implements PropertyListener<Set<String>> { | private static class ServerNamespaceSetPropertyListener implements PropertyListener<Set<String>> { | ||||
@Override | @Override | ||||
@@ -137,6 +167,8 @@ public final class ClusterServerConfigManager { | |||||
ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); | ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); | ||||
return; | return; | ||||
} | } | ||||
newSet = new HashSet<>(newSet); | |||||
newSet.add(ServerConstants.DEFAULT_NAMESPACE); | newSet.add(ServerConstants.DEFAULT_NAMESPACE); | ||||
Set<String> oldSet = ClusterServerConfigManager.namespaceSet; | Set<String> oldSet = ClusterServerConfigManager.namespaceSet; | ||||
@@ -186,6 +218,19 @@ public final class ClusterServerConfigManager { | |||||
} | } | ||||
} | } | ||||
private static void updateTokenServer(ServerTransportConfig config) { | |||||
int newPort = config.getPort(); | |||||
AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)"); | |||||
if (newPort == port) { | |||||
return; | |||||
} | |||||
ClusterServerConfigManager.port = newPort; | |||||
for (ServerTransportConfigObserver observer : TRANSPORT_CONFIG_OBSERVERS) { | |||||
observer.onTransportConfigChange(config); | |||||
} | |||||
} | |||||
private static class ServerGlobalFlowPropertyListener implements PropertyListener<ServerFlowConfig> { | private static class ServerGlobalFlowPropertyListener implements PropertyListener<ServerFlowConfig> { | ||||
@Override | @Override | ||||
@@ -197,62 +242,44 @@ public final class ClusterServerConfigManager { | |||||
public void configLoad(ServerFlowConfig config) { | public void configLoad(ServerFlowConfig config) { | ||||
applyGlobalFlowConfig(config); | applyGlobalFlowConfig(config); | ||||
} | } | ||||
} | |||||
private static synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { | |||||
if (!isValidFlowConfig(config)) { | |||||
RecordLog.warn( | |||||
"[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: " + config); | |||||
return; | |||||
} | |||||
RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: " + config); | |||||
if (config.getExceedCount() != exceedCount) { | |||||
exceedCount = config.getExceedCount(); | |||||
} | |||||
if (config.getMaxOccupyRatio() != maxOccupyRatio) { | |||||
maxOccupyRatio = config.getMaxOccupyRatio(); | |||||
} | |||||
int newIntervalMs = config.getIntervalMs(); | |||||
int newSampleCount = config.getSampleCount(); | |||||
if (newIntervalMs != intervalMs || newSampleCount != sampleCount) { | |||||
if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) { | |||||
RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count"); | |||||
} else { | |||||
intervalMs = newIntervalMs; | |||||
sampleCount = newSampleCount; | |||||
// Reset all the metrics. | |||||
ClusterMetricStatistics.resetFlowMetrics(); | |||||
ClusterParamMetricStatistics.resetFlowMetrics(); | |||||
private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { | |||||
if (!isValidFlowConfig(config)) { | |||||
RecordLog.warn( | |||||
"[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: " + config); | |||||
return; | |||||
} | |||||
RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: " + config); | |||||
if (config.getExceedCount() != exceedCount) { | |||||
exceedCount = config.getExceedCount(); | |||||
} | |||||
if (config.getMaxOccupyRatio() != maxOccupyRatio) { | |||||
maxOccupyRatio = config.getMaxOccupyRatio(); | |||||
} | |||||
int newIntervalMs = config.getIntervalMs(); | |||||
int newSampleCount = config.getSampleCount(); | |||||
if (newIntervalMs != intervalMs || newSampleCount != sampleCount) { | |||||
if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) { | |||||
RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count"); | |||||
} else { | |||||
intervalMs = newIntervalMs; | |||||
sampleCount = newSampleCount; | |||||
// Reset all the metrics. | |||||
ClusterMetricStatistics.resetFlowMetrics(); | |||||
ClusterParamMetricStatistics.resetFlowMetrics(); | |||||
} | |||||
} | } | ||||
} | |||||
} | |||||
public static void updateTokenServer(ServerTransportConfig config) { | |||||
int newPort = config.getPort(); | |||||
AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)"); | |||||
if (newPort == port) { | |||||
return; | |||||
} | |||||
ClusterServerConfigManager.port = newPort; | |||||
for (ServerTransportConfigObserver observer : TRANSPORT_CONFIG_OBSERVERS) { | |||||
observer.onTransportConfigChange(config); | |||||
} | } | ||||
} | } | ||||
public static boolean isValidTransportConfig(ServerTransportConfig config) { | public static boolean isValidTransportConfig(ServerTransportConfig config) { | ||||
return config != null && config.getPort() > 0; | |||||
return config != null && config.getPort() > 0 && config.getPort() <= 65535; | |||||
} | } | ||||
public static boolean isValidFlowConfig(ServerFlowConfig config) { | public static boolean isValidFlowConfig(ServerFlowConfig config) { | ||||
return config != null && config.getMaxOccupyRatio() >= 0 && config.getExceedCount() >= 0; | return config != null && config.getMaxOccupyRatio() >= 0 && config.getExceedCount() >= 0; | ||||
} | } | ||||
public static void addTransportConfigChangeObserver(ServerTransportConfigObserver observer) { | |||||
AssertUtil.notNull(observer, "observer cannot be null"); | |||||
TRANSPORT_CONFIG_OBSERVERS.add(observer); | |||||
} | |||||
public static double getExceedCount(String namespace) { | public static double getExceedCount(String namespace) { | ||||
AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | AssertUtil.notEmpty(namespace, "namespace cannot be empty"); | ||||
ServerFlowConfig config = NAMESPACE_CONF.get(namespace); | ServerFlowConfig config = NAMESPACE_CONF.get(namespace); | ||||
@@ -100,6 +100,12 @@ public final class ConnectionManager { | |||||
return group; | return group; | ||||
} | } | ||||
public static ConnectionGroup getConnectionGroup(String namespace) { | |||||
AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); | |||||
ConnectionGroup group = getOrCreateGroup(namespace); | |||||
return group; | |||||
} | |||||
private static final Object CREATE_LOCK = new Object(); | private static final Object CREATE_LOCK = new Object(); | ||||
private ConnectionManager() {} | private ConnectionManager() {} | ||||
@@ -46,13 +46,11 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { | |||||
public void channelActive(ChannelHandlerContext ctx) throws Exception { | public void channelActive(ChannelHandlerContext ctx) throws Exception { | ||||
globalConnectionPool.createConnection(ctx.channel()); | globalConnectionPool.createConnection(ctx.channel()); | ||||
String remoteAddress = getRemoteAddress(ctx); | String remoteAddress = getRemoteAddress(ctx); | ||||
System.out.println("[TokenServerHandler] Connection established, remote client address: " + remoteAddress); //TODO: DEBUG | |||||
} | } | ||||
@Override | @Override | ||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception { | public void channelInactive(ChannelHandlerContext ctx) throws Exception { | ||||
String remoteAddress = getRemoteAddress(ctx); | String remoteAddress = getRemoteAddress(ctx); | ||||
System.out.println("[TokenServerHandler] Connection inactive, remote client address: " + remoteAddress); //TODO: DEBUG | |||||
globalConnectionPool.remove(ctx.channel()); | globalConnectionPool.remove(ctx.channel()); | ||||
ConnectionManager.removeConnection(remoteAddress); | ConnectionManager.removeConnection(remoteAddress); | ||||
} | } | ||||
@@ -61,7 +59,6 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { | |||||
@SuppressWarnings("unchecked") | @SuppressWarnings("unchecked") | ||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | ||||
globalConnectionPool.refreshLastReadTime(ctx.channel()); | globalConnectionPool.refreshLastReadTime(ctx.channel()); | ||||
System.out.println(String.format("[%s] Server message recv: %s", System.currentTimeMillis(), msg)); //TODO: DEBUG | |||||
if (msg instanceof ClusterRequest) { | if (msg instanceof ClusterRequest) { | ||||
ClusterRequest request = (ClusterRequest)msg; | ClusterRequest request = (ClusterRequest)msg; | ||||
@@ -105,8 +102,6 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { | |||||
int status = ClusterConstants.RESPONSE_STATUS_OK; | int status = ClusterConstants.RESPONSE_STATUS_OK; | ||||
ClusterResponse<Integer> response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); | ClusterResponse<Integer> response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); | ||||
writeResponse(ctx, response); | writeResponse(ctx, response); | ||||
RecordLog.info("[TokenServerHandler] Client <{0}> registered with namespace <{1}>", clientAddress, namespace); | |||||
} | } | ||||
private String getRemoteAddress(ChannelHandlerContext ctx) { | private String getRemoteAddress(ChannelHandlerContext ctx) { | ||||
@@ -0,0 +1,9 @@ | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerFlowConfigHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterParamFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerConfigHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterServerTransportConfigHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyServerNamespaceSetHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.ModifyClusterParamFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.cluster.server.command.handler.FetchClusterServerInfoCommandHandler |
@@ -32,10 +32,9 @@ import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*; | |||||
* @author Eric Zhao | * @author Eric Zhao | ||||
* @since 1.4.0 | * @since 1.4.0 | ||||
*/ | */ | ||||
@Ignore | |||||
public class ClusterFlowCheckerTest { | public class ClusterFlowCheckerTest { | ||||
@Test | |||||
//@Test | |||||
public void testAcquireClusterTokenOccupyPass() { | public void testAcquireClusterTokenOccupyPass() { | ||||
long flowId = 98765L; | long flowId = 98765L; | ||||
final int threshold = 5; | final int threshold = 5; | ||||
@@ -84,6 +84,10 @@ public final class ClusterStateManager { | |||||
mode = CLUSTER_CLIENT; | mode = CLUSTER_CLIENT; | ||||
sleepIfNeeded(); | sleepIfNeeded(); | ||||
lastModified = TimeUtil.currentTimeMillis(); | lastModified = TimeUtil.currentTimeMillis(); | ||||
return startClient(); | |||||
} | |||||
private static boolean startClient() { | |||||
try { | try { | ||||
EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); | EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); | ||||
if (server != null) { | if (server != null) { | ||||
@@ -104,6 +108,23 @@ public final class ClusterStateManager { | |||||
} | } | ||||
} | } | ||||
private static boolean stopClient() { | |||||
try { | |||||
ClusterTokenClient tokenClient = TokenClientProvider.getClient(); | |||||
if (tokenClient != null) { | |||||
tokenClient.stop(); | |||||
RecordLog.info("[ClusterStateManager] Stopping the cluster token client"); | |||||
return true; | |||||
} else { | |||||
RecordLog.warn("[ClusterStateManager] Cannot stop cluster token client (no server SPI found)"); | |||||
return false; | |||||
} | |||||
} catch (Exception ex) { | |||||
RecordLog.warn("[ClusterStateManager] Error when stopping cluster token client", ex); | |||||
return false; | |||||
} | |||||
} | |||||
/** | /** | ||||
* <p> | * <p> | ||||
* Set current mode to server mode. If Sentinel currently works in client mode, | * Set current mode to server mode. If Sentinel currently works in client mode, | ||||
@@ -117,6 +138,10 @@ public final class ClusterStateManager { | |||||
mode = CLUSTER_SERVER; | mode = CLUSTER_SERVER; | ||||
sleepIfNeeded(); | sleepIfNeeded(); | ||||
lastModified = TimeUtil.currentTimeMillis(); | lastModified = TimeUtil.currentTimeMillis(); | ||||
return startServer(); | |||||
} | |||||
private static boolean startServer() { | |||||
try { | try { | ||||
ClusterTokenClient tokenClient = TokenClientProvider.getClient(); | ClusterTokenClient tokenClient = TokenClientProvider.getClient(); | ||||
if (tokenClient != null) { | if (tokenClient != null) { | ||||
@@ -137,6 +162,23 @@ public final class ClusterStateManager { | |||||
} | } | ||||
} | } | ||||
private static boolean stopServer() { | |||||
try { | |||||
EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); | |||||
if (server != null) { | |||||
server.stop(); | |||||
RecordLog.info("[ClusterStateManager] Stopping the cluster server"); | |||||
return true; | |||||
} else { | |||||
RecordLog.warn("[ClusterStateManager] Cannot stop server (no server SPI found)"); | |||||
return false; | |||||
} | |||||
} catch (Exception ex) { | |||||
RecordLog.warn("[ClusterStateManager] Error when stopping server", ex); | |||||
return false; | |||||
} | |||||
} | |||||
/** | /** | ||||
* The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). | * The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). | ||||
* Or we need to wait for a while. | * Or we need to wait for a while. | ||||
@@ -163,12 +205,12 @@ public final class ClusterStateManager { | |||||
private static class ClusterStatePropertyListener implements PropertyListener<Integer> { | private static class ClusterStatePropertyListener implements PropertyListener<Integer> { | ||||
@Override | @Override | ||||
public void configLoad(Integer value) { | |||||
public synchronized void configLoad(Integer value) { | |||||
applyState(value); | applyState(value); | ||||
} | } | ||||
@Override | @Override | ||||
public void configUpdate(Integer value) { | |||||
public synchronized void configUpdate(Integer value) { | |||||
applyState(value); | applyState(value); | ||||
} | } | ||||
} | } | ||||
@@ -177,6 +219,9 @@ public final class ClusterStateManager { | |||||
if (state == null || state < 0) { | if (state == null || state < 0) { | ||||
return false; | return false; | ||||
} | } | ||||
if (state == mode) { | |||||
return true; | |||||
} | |||||
synchronized (UPDATE_LOCK) { | synchronized (UPDATE_LOCK) { | ||||
switch (state) { | switch (state) { | ||||
case CLUSTER_CLIENT: | case CLUSTER_CLIENT: | ||||
@@ -190,5 +235,5 @@ public final class ClusterStateManager { | |||||
} | } | ||||
} | } | ||||
private static final int MIN_INTERVAL = 10 * 1000; | |||||
private static final int MIN_INTERVAL = 5 * 1000; | |||||
} | } |
@@ -24,6 +24,16 @@ import com.alibaba.csp.sentinel.node.Node; | |||||
*/ | */ | ||||
public interface TrafficShapingController { | public interface TrafficShapingController { | ||||
/** | |||||
* Check whether given resource entry can pass with provided count. | |||||
* | |||||
* @param node resource node | |||||
* @param acquireCount count to acquire | |||||
* @param prioritized whether the request is prioritized | |||||
* @return true if the resource entry can pass; false if it should be blocked | |||||
*/ | |||||
boolean canPass(Node node, int acquireCount, boolean prioritized); | |||||
/** | /** | ||||
* Check whether given resource entry can pass with provided count. | * Check whether given resource entry can pass with provided count. | ||||
* | * | ||||
@@ -36,6 +36,11 @@ public class DefaultController implements TrafficShapingController { | |||||
@Override | @Override | ||||
public boolean canPass(Node node, int acquireCount) { | public boolean canPass(Node node, int acquireCount) { | ||||
return canPass(node, acquireCount, false); | |||||
} | |||||
@Override | |||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) { | |||||
int curCount = avgUsedTokens(node); | int curCount = avgUsedTokens(node); | ||||
if (curCount + acquireCount > count) { | if (curCount + acquireCount > count) { | ||||
return false; | return false; | ||||
@@ -51,4 +56,11 @@ public class DefaultController implements TrafficShapingController { | |||||
return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)node.passQps(); | return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)node.passQps(); | ||||
} | } | ||||
private void sleep(int timeMillis) { | |||||
try { | |||||
Thread.sleep(timeMillis); | |||||
} catch (InterruptedException e) { | |||||
// Ignore. | |||||
} | |||||
} | |||||
} | } |
@@ -39,6 +39,11 @@ public class RateLimiterController implements TrafficShapingController { | |||||
@Override | @Override | ||||
public boolean canPass(Node node, int acquireCount) { | public boolean canPass(Node node, int acquireCount) { | ||||
return canPass(node, acquireCount, false); | |||||
} | |||||
@Override | |||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) { | |||||
long currentTime = TimeUtil.currentTimeMillis(); | long currentTime = TimeUtil.currentTimeMillis(); | ||||
// Calculate the interval between every two requests. | // Calculate the interval between every two requests. | ||||
long costTime = Math.round(1.0 * (acquireCount) / count * 1000); | long costTime = Math.round(1.0 * (acquireCount) / count * 1000); | ||||
@@ -107,6 +107,11 @@ public class WarmUpController implements TrafficShapingController { | |||||
@Override | @Override | ||||
public boolean canPass(Node node, int acquireCount) { | public boolean canPass(Node node, int acquireCount) { | ||||
return canPass(node, acquireCount, false); | |||||
} | |||||
@Override | |||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) { | |||||
long passQps = node.passQps(); | long passQps = node.passQps(); | ||||
long previousQps = node.previousPassQps(); | long previousQps = node.previousPassQps(); | ||||
@@ -39,6 +39,11 @@ public class WarmUpRateLimiterController extends WarmUpController { | |||||
@Override | @Override | ||||
public boolean canPass(Node node, int acquireCount) { | public boolean canPass(Node node, int acquireCount) { | ||||
return canPass(node, acquireCount, false); | |||||
} | |||||
@Override | |||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) { | |||||
long previousQps = node.previousPassQps(); | long previousQps = node.previousPassQps(); | ||||
syncToken(previousQps); | syncToken(previousQps); | ||||
@@ -16,6 +16,7 @@ | |||||
package com.alibaba.csp.sentinel.slots.statistic.data; | package com.alibaba.csp.sentinel.slots.statistic.data; | ||||
import com.alibaba.csp.sentinel.Constants; | import com.alibaba.csp.sentinel.Constants; | ||||
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; | import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; | ||||
/** | /** | ||||
@@ -26,15 +27,16 @@ import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; | |||||
*/ | */ | ||||
public class MetricBucket { | public class MetricBucket { | ||||
private final LongAdder pass = new LongAdder(); | |||||
private final LongAdder block = new LongAdder(); | |||||
private final LongAdder exception = new LongAdder(); | |||||
private final LongAdder rt = new LongAdder(); | |||||
private final LongAdder success = new LongAdder(); | |||||
private final LongAdder[] counters; | |||||
private volatile long minRt; | private volatile long minRt; | ||||
public MetricBucket() { | public MetricBucket() { | ||||
MetricEvent[] events = MetricEvent.values(); | |||||
this.counters = new LongAdder[events.length]; | |||||
for (MetricEvent event : events) { | |||||
counters[event.ordinal()] = new LongAdder(); | |||||
} | |||||
initMinRt(); | initMinRt(); | ||||
} | } | ||||
@@ -48,29 +50,36 @@ public class MetricBucket { | |||||
* @return new metric bucket in initial state | * @return new metric bucket in initial state | ||||
*/ | */ | ||||
public MetricBucket reset() { | public MetricBucket reset() { | ||||
pass.reset(); | |||||
block.reset(); | |||||
exception.reset(); | |||||
rt.reset(); | |||||
success.reset(); | |||||
for (MetricEvent event : MetricEvent.values()) { | |||||
counters[event.ordinal()].reset(); | |||||
} | |||||
initMinRt(); | initMinRt(); | ||||
return this; | return this; | ||||
} | } | ||||
public long get(MetricEvent event) { | |||||
return counters[event.ordinal()].sum(); | |||||
} | |||||
public MetricBucket add(MetricEvent event, long n) { | |||||
counters[event.ordinal()].add(n); | |||||
return this; | |||||
} | |||||
public long pass() { | public long pass() { | ||||
return pass.sum(); | |||||
return get(MetricEvent.PASS); | |||||
} | } | ||||
public long block() { | public long block() { | ||||
return block.sum(); | |||||
return get(MetricEvent.BLOCK); | |||||
} | } | ||||
public long exception() { | public long exception() { | ||||
return exception.sum(); | |||||
return get(MetricEvent.EXCEPTION); | |||||
} | } | ||||
public long rt() { | public long rt() { | ||||
return rt.sum(); | |||||
return get(MetricEvent.RT); | |||||
} | } | ||||
public long minRt() { | public long minRt() { | ||||
@@ -78,27 +87,27 @@ public class MetricBucket { | |||||
} | } | ||||
public long success() { | public long success() { | ||||
return success.sum(); | |||||
return get(MetricEvent.SUCCESS); | |||||
} | } | ||||
public void addPass() { | public void addPass() { | ||||
pass.add(1L); | |||||
add(MetricEvent.PASS, 1); | |||||
} | } | ||||
public void addException() { | public void addException() { | ||||
exception.add(1L); | |||||
add(MetricEvent.EXCEPTION, 1); | |||||
} | } | ||||
public void addBlock() { | public void addBlock() { | ||||
block.add(1L); | |||||
add(MetricEvent.BLOCK, 1); | |||||
} | } | ||||
public void addSuccess() { | public void addSuccess() { | ||||
success.add(1L); | |||||
add(MetricEvent.SUCCESS, 1); | |||||
} | } | ||||
public void addRT(long rt) { | public void addRT(long rt) { | ||||
this.rt.add(rt); | |||||
add(MetricEvent.RT, rt); | |||||
// Not thread-safe, but it's okay. | // Not thread-safe, but it's okay. | ||||
if (rt < minRt) { | if (rt < minRt) { | ||||
@@ -13,7 +13,7 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * limitations under the License. | ||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.command.handler; | |||||
package com.alibaba.csp.sentinel.command.handler.cluster; | |||||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | ||||
import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; | import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; |
@@ -13,13 +13,14 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * limitations under the License. | ||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.command.handler; | |||||
package com.alibaba.csp.sentinel.command.handler.cluster; | |||||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | ||||
import com.alibaba.csp.sentinel.command.CommandHandler; | import com.alibaba.csp.sentinel.command.CommandHandler; | ||||
import com.alibaba.csp.sentinel.command.CommandRequest; | import com.alibaba.csp.sentinel.command.CommandRequest; | ||||
import com.alibaba.csp.sentinel.command.CommandResponse; | import com.alibaba.csp.sentinel.command.CommandResponse; | ||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | ||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
/** | /** | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
@@ -32,6 +33,7 @@ public class ModifyClusterModeCommandHandler implements CommandHandler<String> { | |||||
public CommandResponse<String> handle(CommandRequest request) { | public CommandResponse<String> handle(CommandRequest request) { | ||||
try { | try { | ||||
int mode = Integer.valueOf(request.getParam("mode")); | int mode = Integer.valueOf(request.getParam("mode")); | ||||
RecordLog.info("[ModifyClusterModeCommandHandler] Modifying cluster mode to: " + mode); | |||||
if (ClusterStateManager.applyState(mode)) { | if (ClusterStateManager.applyState(mode)) { | ||||
return CommandResponse.ofSuccess("success"); | return CommandResponse.ofSuccess("success"); | ||||
} else { | } else { |
@@ -12,5 +12,5 @@ com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler | com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler | ||||
com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler | com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler | ||||
com.alibaba.csp.sentinel.command.handler.VersionCommandHandler | com.alibaba.csp.sentinel.command.handler.VersionCommandHandler | ||||
com.alibaba.csp.sentinel.command.handler.FetchClusterModeCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.ModifyClusterModeCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler |