From 6545a90d465a79b6e65777b23fe0c690036a9eaa Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 17:55:27 +0800 Subject: [PATCH 01/20] Add basic interface and entity for Sentinel cluster flow control - Add a universal `TokenService` SPI interface for both local flow control and distributed flow control - Add TokenResult entity to represents result of acquiring token - Add `ClusterTokenClient` as the SPI interface for client of Sentinel cluster flow control Signed-off-by: Eric Zhao --- .../sentinel/cluster/ClusterTokenClient.java | 32 +++++++ .../sentinel/cluster/TokenClientProvider.java | 61 +++++++++++++ .../csp/sentinel/cluster/TokenResult.java | 86 +++++++++++++++++++ .../sentinel/cluster/TokenResultStatus.java | 60 +++++++++++++ .../cluster/TokenServerDescriptor.java | 72 ++++++++++++++++ .../csp/sentinel/cluster/TokenService.java | 47 ++++++++++ .../cluster/log/ClusterStatLogUtil.java | 59 +++++++++++++ .../slots/block/flow/FlowSlotTest.java | 16 ++-- 8 files changed, 425 insertions(+), 8 deletions(-) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java new file mode 100644 index 00000000..6a230c07 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java @@ -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; + +/** + * Token client interface for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTokenClient extends TokenService { + + /** + * Get descriptor of current token server. + * + * @return current token server + */ + TokenServerDescriptor currentServer(); +} \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java new file mode 100644 index 00000000..592046c9 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * Provider for a universal {@link ClusterTokenClient} instance. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenClientProvider { + + private static ClusterTokenClient client = null; + + private static final ServiceLoader LOADER = ServiceLoader.load(ClusterTokenClient.class); + + static { + // Not strictly thread-safe, but it's OK since it will be resolved only once. + resolveTokenClientInstance(); + } + + public static ClusterTokenClient getClient() { + return client; + } + + private static void resolveTokenClientInstance() { + List clients = new ArrayList(); + for (ClusterTokenClient client : LOADER) { + clients.add(client); + } + + if (!clients.isEmpty()) { + // Get first. + client = clients.get(0); + RecordLog.info("[TokenClientProvider] Token client resolved: " + client.getClass().getCanonicalName()); + } else { + RecordLog.warn("[TokenClientProvider] No existing token client, resolve failed"); + } + } + + private TokenClientProvider() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java new file mode 100644 index 00000000..29fa064a --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import java.util.Map; + +/** + * Result entity of acquiring cluster flow token. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenResult { + + private Integer status; + + private int remaining; + private int waitInMs; + + private Map attachments; + + public TokenResult() {} + + public TokenResult(Integer status) { + this.status = status; + } + + public Integer getStatus() { + return status; + } + + public TokenResult setStatus(Integer status) { + this.status = status; + return this; + } + + public int getRemaining() { + return remaining; + } + + public TokenResult setRemaining(int remaining) { + this.remaining = remaining; + return this; + } + + public int getWaitInMs() { + return waitInMs; + } + + public TokenResult setWaitInMs(int waitInMs) { + this.waitInMs = waitInMs; + return this; + } + + public Map getAttachments() { + return attachments; + } + + public TokenResult setAttachments(Map attachments) { + this.attachments = attachments; + return this; + } + + @Override + public String toString() { + return "TokenResult{" + + "status=" + status + + ", remaining=" + remaining + + ", waitInMs=" + waitInMs + + ", attachments=" + attachments + + '}'; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java new file mode 100644 index 00000000..275d761e --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenResultStatus { + + /** + * Bad client request. + */ + public static final int BAD_REQUEST = -4; + /** + * Server or client unexpected failure (due to transport or serialization failure). + */ + public static final int FAIL = -1; + + /** + * Token acquired. + */ + public static final int OK = 0; + + /** + * Token acquire failed (blocked). + */ + public static final int BLOCKED = 1; + /** + * Should wait for next buckets. + */ + public static final int SHOULD_WAIT = 2; + /** + * Token acquire failed (no rule exists). + */ + public static final int NO_RULE_EXISTS = 3; + /** + * Token acquire failed (reference resource is not available). + */ + public static final int NO_REF_RULE_EXISTS = 4; + /** + * Token acquire failed (strategy not available). + */ + public static final int NOT_AVAILABLE = 5; + + private TokenResultStatus() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java new file mode 100644 index 00000000..98739179 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java @@ -0,0 +1,72 @@ +/* + * 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; + +/** + * A simple descriptor for Sentinel token server. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenServerDescriptor { + + private String host; + private int port; + private String type; + + public TokenServerDescriptor() {} + + public TokenServerDescriptor(String host, int port) { + this.host = host; + this.port = port; + } + + public String getHost() { + return host; + } + + public TokenServerDescriptor setHost(String host) { + this.host = host; + return this; + } + + public int getPort() { + return port; + } + + public TokenServerDescriptor setPort(int port) { + this.port = port; + return this; + } + + public String getType() { + return type; + } + + public TokenServerDescriptor setType(String type) { + this.type = type; + return this; + } + + @Override + public String toString() { + return "TokenServerDescriptor{" + + "host='" + host + '\'' + + ", port=" + port + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java new file mode 100644 index 00000000..d02c562f --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import java.util.Collection; + +/** + * Service interface of flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface TokenService { + + /** + * Request tokens from remote token server. + * + * @param ruleId the unique rule ID + * @param acquireCount token count to acquire + * @param prioritized whether the request is prioritized + * @return result of the token request + */ + TokenResult requestToken(Integer ruleId, int acquireCount, boolean prioritized); + + /** + * Request tokens for a specific parameter from remote token server. + * + * @param ruleId the unique rule ID + * @param acquireCount token count to acquire + * @param params parameter list + * @return result of the token request + */ + TokenResult requestParamToken(Integer ruleId, int acquireCount, Collection params); +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java new file mode 100644 index 00000000..16f914c6 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java @@ -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.log; + +import java.io.File; + +import com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author jialiang.linjl + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterStatLogUtil { + + private static final String FILE_NAME = "sentinel-cluster.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterStatLogUtil() {} +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java index 552f8407..ee5de0a8 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java @@ -54,7 +54,7 @@ public class FlowSlotTest { Context context = mock(Context.class); DefaultNode node = mock(DefaultNode.class); doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), - any(DefaultNode.class), anyInt()); + any(DefaultNode.class), anyInt(), anyBoolean()); String resA = "resAK"; String resB = "resBK"; @@ -63,13 +63,13 @@ public class FlowSlotTest { // Here we only load rules for resA. FlowRuleManager.loadRules(Collections.singletonList(rule1)); - when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt())) + when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(true); - when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt())) + when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(false); - flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1); - flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1); + flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); + flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1, false); } @Test(expected = FlowException.class) @@ -78,15 +78,15 @@ public class FlowSlotTest { Context context = mock(Context.class); DefaultNode node = mock(DefaultNode.class); doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class), - any(DefaultNode.class), anyInt()); + any(DefaultNode.class), anyInt(), anyBoolean()); String resA = "resAK"; FlowRule rule = new FlowRule(resA).setCount(10); FlowRuleManager.loadRules(Collections.singletonList(rule)); - when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt())) + when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean())) .thenReturn(false); - flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1); + flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false); } } \ No newline at end of file From b215e8784e2fd3f7f197530c5198e693c9b33154 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 17:59:40 +0800 Subject: [PATCH 02/20] Update flow rule for cluster mode and extract util class - Add new field `clusterMode` and `clusterConfig` for cluster mode - Add a `ClusterFlowConfig` class for specific items for cluster flow control - Update FlowRuleChecker to support cluster mode - Extract valid rule checking and rule map generating logic to FlowRuleUtil Signed-off-by: Eric Zhao --- .../slots/block/ClusterRuleConstant.java | 31 +++ .../slots/block/flow/ClusterFlowConfig.java | 154 ++++++++++++ .../sentinel/slots/block/flow/FlowRule.java | 81 ++++--- .../slots/block/flow/FlowRuleChecker.java | 53 +++- .../slots/block/flow/FlowRuleComparator.java | 13 + .../slots/block/flow/FlowRuleManager.java | 123 ++-------- .../slots/block/flow/FlowRuleUtil.java | 227 ++++++++++++++++++ .../sentinel/slots/block/flow/FlowSlot.java | 12 +- 8 files changed, 541 insertions(+), 153 deletions(-) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java new file mode 100644 index 00000000..78cacf34 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java @@ -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.slots.block; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterRuleConstant { + + public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0; + public static final int FLOW_CLUSTER_STRATEGY_REF = 1; + + public static final int FLOW_THRESHOLD_AVG_LOCAL = 0; + public static final int FLOW_THRESHOLD_GLOBAL = 1; + + private ClusterRuleConstant() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java new file mode 100644 index 00000000..506701e2 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -0,0 +1,154 @@ +/* + * 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.slots.block.flow; + +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; + +/** + * Flow rule config in cluster mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterFlowConfig { + + /** + * Global unique ID. + */ + private Integer flowId; + + /** + * Threshold type (average by local value or global value). + */ + private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; + private boolean fallbackToLocalWhenFail; + + /** + * 0: normal; 1: using reference (borrow from reference). + */ + private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; + + private Integer refFlowId; + private int refSampleCount = 10; + private double refRatio = 1d; + + public Integer getFlowId() { + return flowId; + } + + public ClusterFlowConfig setFlowId(Integer flowId) { + this.flowId = flowId; + return this; + } + + public int getThresholdType() { + return thresholdType; + } + + public ClusterFlowConfig setThresholdType(int thresholdType) { + this.thresholdType = thresholdType; + return this; + } + + public int getStrategy() { + return strategy; + } + + public ClusterFlowConfig setStrategy(int strategy) { + this.strategy = strategy; + return this; + } + + public Integer getRefFlowId() { + return refFlowId; + } + + public ClusterFlowConfig setRefFlowId(Integer refFlowId) { + this.refFlowId = refFlowId; + return this; + } + + public int getRefSampleCount() { + return refSampleCount; + } + + public ClusterFlowConfig setRefSampleCount(int refSampleCount) { + this.refSampleCount = refSampleCount; + return this; + } + + public double getRefRatio() { + return refRatio; + } + + public ClusterFlowConfig setRefRatio(double refRatio) { + this.refRatio = refRatio; + return this; + } + + public boolean isFallbackToLocalWhenFail() { + return fallbackToLocalWhenFail; + } + + public ClusterFlowConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) { + this.fallbackToLocalWhenFail = fallbackToLocalWhenFail; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + + ClusterFlowConfig that = (ClusterFlowConfig)o; + + if (thresholdType != that.thresholdType) { return false; } + if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } + if (strategy != that.strategy) { return false; } + if (refSampleCount != that.refSampleCount) { return false; } + if (Double.compare(that.refRatio, refRatio) != 0) { return false; } + if (flowId != null ? !flowId.equals(that.flowId) : that.flowId != null) { return false; } + return refFlowId != null ? refFlowId.equals(that.refFlowId) : that.refFlowId == null; + } + + @Override + public int hashCode() { + int result; + long temp; + result = flowId != null ? flowId.hashCode() : 0; + result = 31 * result + thresholdType; + result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); + result = 31 * result + strategy; + result = 31 * result + (refFlowId != null ? refFlowId.hashCode() : 0); + result = 31 * result + refSampleCount; + temp = Double.doubleToLongBits(refRatio); + result = 31 * result + (int)(temp ^ (temp >>> 32)); + return result; + } + + @Override + public String toString() { + return "ClusterFlowConfig{" + + "flowId=" + flowId + + ", thresholdType=" + thresholdType + + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + + ", strategy=" + strategy + + ", refFlowId=" + refFlowId + + ", refSampleCount=" + refSampleCount + + ", refRatio=" + refRatio + + '}'; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java index dc42a89e..e7413ea2 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java @@ -85,6 +85,12 @@ public class FlowRule extends AbstractRule { */ private int maxQueueingTimeMs = 500; + private boolean clusterMode; + /** + * Flow rule config for cluster mode. + */ + private ClusterFlowConfig clusterConfig; + /** * The traffic shaping (throttling) controller. */ @@ -162,6 +168,24 @@ public class FlowRule extends AbstractRule { return this; } + public boolean isClusterMode() { + return clusterMode; + } + + public FlowRule setClusterMode(boolean clusterMode) { + this.clusterMode = clusterMode; + return this; + } + + public ClusterFlowConfig getClusterConfig() { + return clusterConfig; + } + + public FlowRule setClusterConfig(ClusterFlowConfig clusterConfig) { + this.clusterConfig = clusterConfig; + return this; + } + @Override public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { return true; @@ -169,43 +193,21 @@ public class FlowRule extends AbstractRule { @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof FlowRule)) { - return false; - } - if (!super.equals(o)) { - return false; - } + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + if (!super.equals(o)) { return false; } - FlowRule flowRule = (FlowRule)o; + FlowRule rule = (FlowRule)o; - if (grade != flowRule.grade) { - return false; - } - if (Double.compare(flowRule.count, count) != 0) { - return false; - } - if (strategy != flowRule.strategy) { - return false; - } - if (refResource != null ? !refResource.equals(flowRule.refResource) : flowRule.refResource != null) { - return false; - } - if (this.controlBehavior != flowRule.controlBehavior) { - return false; - } - - if (warmUpPeriodSec != flowRule.warmUpPeriodSec) { - return false; - } - - if (maxQueueingTimeMs != flowRule.maxQueueingTimeMs) { - return false; - } - - return true; + if (grade != rule.grade) { return false; } + if (Double.compare(rule.count, count) != 0) { return false; } + if (strategy != rule.strategy) { return false; } + if (controlBehavior != rule.controlBehavior) { return false; } + if (warmUpPeriodSec != rule.warmUpPeriodSec) { return false; } + if (maxQueueingTimeMs != rule.maxQueueingTimeMs) { return false; } + if (clusterMode != rule.clusterMode) { return false; } + if (refResource != null ? !refResource.equals(rule.refResource) : rule.refResource != null) { return false; } + return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null; } @Override @@ -217,10 +219,11 @@ public class FlowRule extends AbstractRule { result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + strategy; result = 31 * result + (refResource != null ? refResource.hashCode() : 0); - result = 31 * result + (int)(temp ^ (temp >>> 32)); - result = 31 * result + warmUpPeriodSec; result = 31 * result + controlBehavior; + result = 31 * result + warmUpPeriodSec; result = 31 * result + maxQueueingTimeMs; + result = 31 * result + (clusterMode ? 1 : 0); + result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0); return result; } @@ -236,7 +239,9 @@ public class FlowRule extends AbstractRule { ", controlBehavior=" + controlBehavior + ", warmUpPeriodSec=" + warmUpPeriodSec + ", maxQueueingTimeMs=" + maxQueueingTimeMs + + ", clusterMode=" + clusterMode + + ", clusterConfig=" + clusterConfig + ", controller=" + controller + - "}"; + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java index 197a3bbf..7518d7c1 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java @@ -15,7 +15,12 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; +import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slots.block.RuleConstant; @@ -30,11 +35,25 @@ import com.alibaba.csp.sentinel.util.StringUtil; final class FlowRuleChecker { static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount) { + return passCheck(rule, context, node, acquireCount, false); + } + + static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { String limitApp = rule.getLimitApp(); if (limitApp == null) { return true; } + if (rule.isClusterMode()) { + return passClusterCheck(rule, context, node, acquireCount, prioritized); + } + + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } + + private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); if (selectedNode == null) { return true; @@ -43,6 +62,38 @@ final class FlowRuleChecker { return rule.getRater().canPass(selectedNode, acquireCount); } + static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + try { + ClusterTokenClient client = TokenClientProvider.getClient(); + if (client != null) { + TokenResult result = client.requestToken(rule.getClusterConfig().getFlowId(), acquireCount, + prioritized); + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.SHOULD_WAIT: + // Wait for next tick. + Thread.sleep(result.getWaitInMs()); + return true; + case TokenResultStatus.NO_RULE_EXISTS: + case TokenResultStatus.BAD_REQUEST: + case TokenResultStatus.FAIL: + return passLocalCheck(rule, context, node, acquireCount, prioritized); + case TokenResultStatus.BLOCKED: + default: + return false; + } + } + // If client is absent, then fallback to local mode. + } catch (Throwable ex) { + RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); + } + // TODO: choose whether fallback to local or inactivate the rule. + // Downgrade to local flow control when token client or server for this rule is not available. + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } + static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { String refResource = rule.getRefResource(); int strategy = rule.getStrategy(); @@ -103,4 +154,4 @@ final class FlowRuleChecker { } private FlowRuleChecker() {} -} +} \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java index 688bed96..ed1b22c6 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java @@ -19,10 +19,23 @@ import java.util.Comparator; import com.alibaba.csp.sentinel.slots.block.RuleConstant; +/** + * Comparator for flow rules. + * + * @author jialiang.linjl + */ public class FlowRuleComparator implements Comparator { @Override public int compare(FlowRule o1, FlowRule o2) { + // Clustered mode will be on the top. + if (o1.isClusterMode() && !o2.isClusterMode()) { + return 1; + } + + if (!o1.isClusterMode() && o2.isClusterMode()) { + return -1; + } if (o1.getLimitApp() == null) { return 0; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java index 230b4679..9dd23026 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java @@ -16,8 +16,6 @@ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -32,16 +30,10 @@ import com.alibaba.csp.sentinel.node.metric.MetricTimerListener; 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.controller.DefaultController; -import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController; -import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController; -import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController; /** *

- * One resources can have multiple rules. And these rules take effects in the - * following order: + * One resources can have multiple rules. And these rules take effects in the following order: *

    *
  1. requests from specified caller
  2. *
  3. no specified caller
  4. @@ -49,18 +41,21 @@ import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterCon *

    * * @author jialiang.linjl + * @author Eric Zhao */ public class FlowRuleManager { private static final Map> flowRules = new ConcurrentHashMap>(); - private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, - new NamedThreadFactory("sentinel-metrics-record-task", true)); - private final static FlowPropertyListener listener = new FlowPropertyListener(); + + private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); + private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("sentinel-metrics-record-task", true)); + static { - currentProperty.addListener(listener); - scheduler.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS); + currentProperty.addListener(LISTENER); + SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS); } /** @@ -70,10 +65,10 @@ public class FlowRuleManager { * @param property the property to listen. */ public static void register2Property(SentinelProperty> property) { - synchronized (listener) { + synchronized (LISTENER) { RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager"); - currentProperty.removeListener(listener); - property.addListener(listener); + currentProperty.removeListener(LISTENER); + property.addListener(LISTENER); currentProperty = property; } } @@ -100,65 +95,7 @@ public class FlowRuleManager { currentProperty.updateValue(rules); } - private static Map> loadFlowConf(List list) { - Map> newRuleMap = new ConcurrentHashMap>(); - - if (list == null || list.isEmpty()) { - return newRuleMap; - } - - for (FlowRule rule : list) { - if (!isValidRule(rule)) { - RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); - continue; - } - if (StringUtil.isBlank(rule.getLimitApp())) { - rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); - } - - TrafficShapingController rater = new DefaultController(rule.getCount(), rule.getGrade()); - if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS - && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP - && rule.getWarmUpPeriodSec() > 0) { - rater = new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), ColdFactorProperty.coldFactor); - - } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS - && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER - && rule.getMaxQueueingTimeMs() > 0) { - rater = new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount()); - } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS - && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER - && rule.getMaxQueueingTimeMs() > 0 && rule.getWarmUpPeriodSec() > 0) { - rater = new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(), - rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); - } - - rule.setRater(rater); - - String identity = rule.getResource(); - List ruleM = newRuleMap.get(identity); - - if (ruleM == null) { - ruleM = new ArrayList(); - newRuleMap.put(identity, ruleM); - } - - ruleM.add(rule); - - } - - if (!newRuleMap.isEmpty()) { - Comparator comparator = new FlowRuleComparator(); - // Sort the rules. - for (List rules : newRuleMap.values()) { - Collections.sort(rules, comparator); - } - } - - return newRuleMap; - } - - static Map> getFlowRules() { + static Map> getFlowRuleMap() { return flowRules; } @@ -188,7 +125,7 @@ public class FlowRuleManager { @Override public void configUpdate(List value) { - Map> rules = loadFlowConf(value); + Map> rules = FlowRuleUtil.buildFlowRuleMap(value); if (rules != null) { flowRules.clear(); flowRules.putAll(rules); @@ -198,43 +135,13 @@ public class FlowRuleManager { @Override public void configLoad(List conf) { - Map> rules = loadFlowConf(conf); + Map> rules = FlowRuleUtil.buildFlowRuleMap(conf); if (rules != null) { flowRules.clear(); flowRules.putAll(rules); } RecordLog.info("[FlowRuleManager] Flow rules loaded: " + flowRules); } - } - public static boolean isValidRule(FlowRule rule) { - boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 - && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; - if (!baseValid) { - return false; - } - // Check strategy and control (shaping) behavior. - return checkStrategyField(rule) && checkControlBehaviorField(rule); - } - - private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) { - if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) { - return StringUtil.isNotBlank(rule.getRefResource()); - } - return true; - } - - private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) { - switch (rule.getControlBehavior()) { - case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: - return rule.getWarmUpPeriodSec() > 0; - case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: - return rule.getMaxQueueingTimeMs() > 0; - case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: - return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0; - default: - return true; - } - } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java new file mode 100644 index 00000000..aa5a58c6 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -0,0 +1,227 @@ +/* + * 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.slots.block.flow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; +import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController; +import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController; +import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Function; +import com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class FlowRuleUtil { + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @return constructed new flow rule map; empty map if list is null or empty, or no valid rules + */ + public static Map> buildFlowRuleMap(List list) { + return buildFlowRuleMap(list, null); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @param filter rule filter + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Predicate filter) { + return buildFlowRuleMap(list, filter, true); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @param filter rule filter + * @param shouldSort whether the rules should be sorted + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Predicate filter, + boolean shouldSort) { + return buildFlowRuleMap(list, extractResource, filter, shouldSort); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by provided group function. + * + * @param list raw list of flow rules + * @param groupFunction grouping function of the map (by key) + * @param filter rule filter + * @param shouldSort whether the rules should be sorted + * @param type of key + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Function groupFunction, + Predicate filter, boolean shouldSort) { + Map> newRuleMap = new ConcurrentHashMap>(); + if (list == null || list.isEmpty()) { + return newRuleMap; + } + + for (FlowRule rule : list) { + if (!isValidRule(rule)) { + RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); + continue; + } + if (filter != null && !filter.test(rule)) { + continue; + } + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + TrafficShapingController rater = generateRater(rule); + rule.setRater(rater); + + K key = groupFunction.apply(rule); + if (key == null) { + continue; + } + List flowRules = newRuleMap.get(key); + + if (flowRules == null) { + flowRules = new ArrayList(); + newRuleMap.put(key, flowRules); + } + + flowRules.add(rule); + } + + if (shouldSort && !newRuleMap.isEmpty()) { + Comparator comparator = new FlowRuleComparator(); + // Sort the rules. + for (List rules : newRuleMap.values()) { + Collections.sort(rules, comparator); + } + } + + return newRuleMap; + } + + private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) { + if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { + switch (rule.getControlBehavior()) { + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: + return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), + ColdFactorProperty.coldFactor); + case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: + return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount()); + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: + return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(), + rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); + case RuleConstant.CONTROL_BEHAVIOR_DEFAULT: + default: + // Default mode or unknown mode: default traffic shaping controller (fast-reject). + } + } + return new DefaultController(rule.getCount(), rule.getGrade()); + } + + /** + * Check whether provided ID can be a valid cluster flow ID. + * + * @param id flow ID to check + * @return true if valid, otherwise false + */ + public static boolean validClusterRuleId(Integer id) { + return id != null && id > 0; + } + + /** + * Check whether provided flow rule is valid. + * + * @param rule flow rule to check + * @return true if valid, otherwise false + */ + public static boolean isValidRule(FlowRule rule) { + boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 + && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; + if (!baseValid) { + return false; + } + // Check strategy and control (shaping) behavior. + return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule); + } + + private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) { + if (!rule.isClusterMode()) { + return true; + } + ClusterFlowConfig clusterConfig = rule.getClusterConfig(); + if (clusterConfig == null) { + return false; + } + if (!validClusterRuleId(clusterConfig.getFlowId())) { + return false; + } + switch (rule.getStrategy()) { + case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL: + return true; + case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF: + return validClusterRuleId(clusterConfig.getRefFlowId()); + default: + return true; + } + } + + private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) { + if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) { + return StringUtil.isNotBlank(rule.getRefResource()); + } + return true; + } + + private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) { + switch (rule.getControlBehavior()) { + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: + return rule.getWarmUpPeriodSec() > 0; + case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: + return rule.getMaxQueueingTimeMs() > 0; + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: + return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0; + default: + return true; + } + } + + private static final Function extractResource = new Function() { + @Override + public String apply(FlowRule rule) { + return rule.getResource(); + } + }; + + private FlowRuleUtil() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java index 4bd8ca5d..3f392db1 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java @@ -138,27 +138,27 @@ public class FlowSlot extends AbstractLinkedProcessorSlot { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { - checkFlow(resourceWrapper, context, node, count); + checkFlow(resourceWrapper, context, node, count, prioritized); fireEntry(context, resourceWrapper, node, count, prioritized, args); } - void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count) throws BlockException { + void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException { // Flow rule map cannot be null. - Map> flowRules = FlowRuleManager.getFlowRules(); + Map> flowRules = FlowRuleManager.getFlowRuleMap(); List rules = flowRules.get(resource.getName()); if (rules != null) { for (FlowRule rule : rules) { - if (!canPassCheck(rule, context, node, count)) { + if (!canPassCheck(rule, context, node, count, prioritized)) { throw new FlowException(rule.getLimitApp()); } } } } - boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count) { - return FlowRuleChecker.passCheck(rule, context, node, count); + boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count, boolean prioritized) { + return FlowRuleChecker.passCheck(rule, context, node, count, prioritized); } @Override From 1043648fbc7afe97063ea52adea95fc2607f45d8 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 19:02:21 +0800 Subject: [PATCH 03/20] Update parameter flow rule to adapt to cluster mode and extract rule util class - Update ParamFlowRule to support cluster mode - Add `ParamFlowClusterConfig` to provide cluster mode items for the rule - Update ParamFlowChecker to support cluster flow mode - Extract ParamFlowRuleUtil class - Change type of `flowId` from Integer to Long Signed-off-by: Eric Zhao --- .../csp/sentinel/cluster/TokenService.java | 4 +- .../slots/block/flow/ClusterFlowConfig.java | 12 +- .../slots/block/flow/FlowRuleUtil.java | 2 +- .../block/flow/param/ParamFlowChecker.java | 79 +++++++++++- .../flow/param/ParamFlowClusterConfig.java | 94 +++++++++++++++ .../slots/block/flow/param/ParamFlowRule.java | 35 +++++- .../flow/param/ParamFlowRuleManager.java | 68 +---------- .../block/flow/param/ParamFlowRuleUtil.java | 114 ++++++++++++++++++ .../flow/param/ParamFlowRuleManagerTest.java | 82 ------------- .../flow/param/ParamFlowRuleUtilTest.java | 95 +++++++++++++++ 10 files changed, 419 insertions(+), 166 deletions(-) create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java index d02c562f..f1d06292 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java @@ -33,7 +33,7 @@ public interface TokenService { * @param prioritized whether the request is prioritized * @return result of the token request */ - TokenResult requestToken(Integer ruleId, int acquireCount, boolean prioritized); + TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized); /** * Request tokens for a specific parameter from remote token server. @@ -43,5 +43,5 @@ public interface TokenService { * @param params parameter list * @return result of the token request */ - TokenResult requestParamToken(Integer ruleId, int acquireCount, Collection params); + TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java index 506701e2..b12a0743 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -28,7 +28,7 @@ public class ClusterFlowConfig { /** * Global unique ID. */ - private Integer flowId; + private Long flowId; /** * Threshold type (average by local value or global value). @@ -41,15 +41,15 @@ public class ClusterFlowConfig { */ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; - private Integer refFlowId; + private Long refFlowId; private int refSampleCount = 10; private double refRatio = 1d; - public Integer getFlowId() { + public Long getFlowId() { return flowId; } - public ClusterFlowConfig setFlowId(Integer flowId) { + public ClusterFlowConfig setFlowId(Long flowId) { this.flowId = flowId; return this; } @@ -72,11 +72,11 @@ public class ClusterFlowConfig { return this; } - public Integer getRefFlowId() { + public Long getRefFlowId() { return refFlowId; } - public ClusterFlowConfig setRefFlowId(Integer refFlowId) { + public ClusterFlowConfig setRefFlowId(Long refFlowId) { this.refFlowId = refFlowId; return this; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java index aa5a58c6..bea269f1 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -155,7 +155,7 @@ public final class FlowRuleUtil { * @param id flow ID to check * @return true if valid, otherwise false */ - public static boolean validClusterRuleId(Integer id) { + public static boolean validClusterRuleId(Long id) { return id != null && id > 0; } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java index f98ef04d..2488643f 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java @@ -16,14 +16,23 @@ package com.alibaba.csp.sentinel.slots.block.flow.param; import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Set; +import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.RuleConstant; /** + * Rule checker for parameter flow control. + * * @author Eric Zhao * @since 0.2.0 */ @@ -40,17 +49,72 @@ final class ParamFlowChecker { return true; } + // Get parameter value. If value is null, then pass. Object value = args[paramIdx]; + if (value == null) { + return true; + } + + if (rule.isClusterMode()) { + return passClusterCheck(resourceWrapper, rule, count, value); + } return passLocalCheck(resourceWrapper, rule, count, value); } - private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) { - // Should not be null. - return ParamFlowSlot.getParamMetric(resourceWrapper); + @SuppressWarnings("unchecked") + private static Collection toCollection(Object value) { + if (value instanceof Collection) { + return (Collection)value; + } else if (value.getClass().isArray()) { + List params = new ArrayList(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + Object param = Array.get(value, i); + params.add(param); + } + return params; + } else { + return Collections.singletonList(value); + } } - private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { + private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + try { + ClusterTokenClient client = TokenClientProvider.getClient(); + if (client == null) { + return true; + } + Collection params = toCollection(value); + + TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.BLOCKED: + return false; + default: + return fallbackToLocalOrPass(resourceWrapper, rule, count, params); + } + } catch (Throwable ex) { + RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); + return fallbackToLocalOrPass(resourceWrapper, rule, count, value); + } + } + + private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(resourceWrapper, rule, count, value); + } else { + // The rule won't be activated, just pass. + return true; + } + } + + private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { try { if (Collection.class.isAssignableFrom(value.getClass())) { for (Object param : ((Collection)value)) { @@ -70,7 +134,7 @@ final class ParamFlowChecker { return passSingleValueCheck(resourceWrapper, rule, count, value); } } catch (Throwable e) { - RecordLog.info("[ParamFlowChecker] Unexpected error", e); + RecordLog.warn("[ParamFlowChecker] Unexpected error", e); } return true; @@ -96,5 +160,10 @@ final class ParamFlowChecker { return true; } + private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) { + // Should not be null. + return ParamFlowSlot.getParamMetric(resourceWrapper); + } + private ParamFlowChecker() {} } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java new file mode 100644 index 00000000..2a532df6 --- /dev/null +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java @@ -0,0 +1,94 @@ +/* + * 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.slots.block.flow.param; + +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; + +/** + * Parameter flow rule config in cluster mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ParamFlowClusterConfig { + + /** + * Global unique ID. + */ + private Long flowId; + + /** + * Threshold type (average by local value or global value). + */ + private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; + private boolean fallbackToLocalWhenFail = false; + + public Long getFlowId() { + return flowId; + } + + public ParamFlowClusterConfig setFlowId(Long flowId) { + this.flowId = flowId; + return this; + } + + public int getThresholdType() { + return thresholdType; + } + + public ParamFlowClusterConfig setThresholdType(int thresholdType) { + this.thresholdType = thresholdType; + return this; + } + + public boolean isFallbackToLocalWhenFail() { + return fallbackToLocalWhenFail; + } + + public ParamFlowClusterConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) { + this.fallbackToLocalWhenFail = fallbackToLocalWhenFail; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + + ParamFlowClusterConfig that = (ParamFlowClusterConfig)o; + + if (thresholdType != that.thresholdType) { return false; } + if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } + return flowId != null ? flowId.equals(that.flowId) : that.flowId == null; + } + + @Override + public int hashCode() { + int result = flowId != null ? flowId.hashCode() : 0; + result = 31 * result + thresholdType; + result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "ParamFlowClusterConfig{" + + "flowId=" + flowId + + ", thresholdType=" + thresholdType + + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + + '}'; + } +} diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java index 51f7b16a..18f6db16 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java @@ -65,6 +65,9 @@ public class ParamFlowRule extends AbstractRule { */ private Map hotItems = new HashMap(); + private boolean clusterMode = false; + private ParamFlowClusterConfig clusterConfig; + public int getGrade() { return grade; } @@ -110,6 +113,25 @@ public class ParamFlowRule extends AbstractRule { return this; } + public boolean isClusterMode() { + return clusterMode; + } + + public ParamFlowRule setClusterMode(boolean clusterMode) { + this.clusterMode = clusterMode; + return this; + } + + public ParamFlowClusterConfig getClusterConfig() { + return clusterConfig; + } + + public ParamFlowRule setClusterConfig( + ParamFlowClusterConfig clusterConfig) { + this.clusterConfig = clusterConfig; + return this; + } + @Override @Deprecated public boolean passCheck(Context context, DefaultNode node, int count, Object... args) { @@ -126,8 +148,11 @@ public class ParamFlowRule extends AbstractRule { if (grade != rule.grade) { return false; } if (Double.compare(rule.count, count) != 0) { return false; } + if (clusterMode != rule.clusterMode) { return false; } if (paramIdx != null ? !paramIdx.equals(rule.paramIdx) : rule.paramIdx != null) { return false; } - return paramFlowItemList != null ? paramFlowItemList.equals(rule.paramFlowItemList) : rule.paramFlowItemList == null; + if (paramFlowItemList != null ? !paramFlowItemList.equals(rule.paramFlowItemList) + : rule.paramFlowItemList != null) { return false; } + return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null; } @Override @@ -139,18 +164,20 @@ public class ParamFlowRule extends AbstractRule { temp = Double.doubleToLongBits(count); result = 31 * result + (int)(temp ^ (temp >>> 32)); result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0); + result = 31 * result + (clusterMode ? 1 : 0); + result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0); return result; } @Override public String toString() { return "ParamFlowRule{" + - "resource=" + getResource() + - ", limitApp=" + getLimitApp() + - ", grade=" + grade + + "grade=" + grade + ", paramIdx=" + paramIdx + ", count=" + count + ", paramFlowItemList=" + paramFlowItemList + + ", clusterMode=" + clusterMode + + ", clusterConfig=" + clusterConfig + '}'; } } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java index b10b260a..3f60b840 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java @@ -99,37 +99,6 @@ public final class ParamFlowRuleManager { return rules; } - private static Object parseValue(String value, String classType) { - if (value == null) { - throw new IllegalArgumentException("Null value"); - } - if (StringUtil.isBlank(classType)) { - // If the class type is not provided, then treat it as string. - return value; - } - // Handle primitive type. - if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) { - return Integer.parseInt(value); - } else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) { - return Boolean.parseBoolean(value); - } else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) { - return Long.parseLong(value); - } else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) { - return Double.parseDouble(value); - } else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) { - return Float.parseFloat(value); - } else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) { - return Byte.parseByte(value); - } else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) { - return Short.parseShort(value); - } else if (char.class.toString().equals(classType)) { - char[] array = value.toCharArray(); - return array.length > 0 ? array[0] : null; - } - - return value; - } - static class RulePropertyListener implements PropertyListener> { @Override @@ -163,7 +132,7 @@ public final class ParamFlowRuleManager { } for (ParamFlowRule rule : list) { - if (!isValidRule(rule)) { + if (!ParamFlowRuleUtil.isValidRule(rule)) { RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule); continue; } @@ -172,12 +141,7 @@ public final class ParamFlowRuleManager { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } - if (rule.getParamFlowItemList() == null) { - rule.setParamFlowItemList(new ArrayList()); - } - - Map itemMap = parseHotItems(rule.getParamFlowItemList()); - rule.setParsedHotItems(itemMap); + ParamFlowRuleUtil.fillExceptionFlowItems(rule); String resourceName = rule.getResource(); List ruleList = newRuleMap.get(resourceName); @@ -200,34 +164,6 @@ public final class ParamFlowRuleManager { } } - static Map parseHotItems(List items) { - Map itemMap = new HashMap(); - if (items == null || items.isEmpty()) { - return itemMap; - } - for (ParamFlowItem item : items) { - // Value should not be null. - Object value; - try { - value = parseValue(item.getObject(), item.getClassType()); - } catch (Exception ex) { - RecordLog.warn("[ParamFlowRuleManager] Failed to parse value for item: " + item, ex); - continue; - } - if (item.getCount() == null || item.getCount() < 0 || value == null) { - RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid exclusion parameter item: " + item); - continue; - } - itemMap.put(value, item.getCount()); - } - return itemMap; - } - - static boolean isValidRule(ParamFlowRule rule) { - return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 - && rule.getParamIdx() != null && rule.getParamIdx() >= 0; - } - private ParamFlowRuleManager() {} } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java new file mode 100644 index 00000000..ec97b19c --- /dev/null +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java @@ -0,0 +1,114 @@ +/* + * 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.slots.block.flow.param; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + * @author Eric Zhao + */ +public final class ParamFlowRuleUtil { + + public static boolean isValidRule(ParamFlowRule rule) { + return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 + && rule.getParamIdx() != null && rule.getParamIdx() >= 0 && checkCluster(rule); + } + + private static boolean checkCluster(/*@PreChecked*/ ParamFlowRule rule) { + if (!rule.isClusterMode()) { + return true; + } + ParamFlowClusterConfig clusterConfig = rule.getClusterConfig(); + return clusterConfig != null && validClusterRuleId(clusterConfig.getFlowId()); + } + + public static boolean validClusterRuleId(Long id) { + return id != null && id > 0; + } + + public static void fillExceptionFlowItems(ParamFlowRule rule) { + if (rule != null) { + if (rule.getParamFlowItemList() == null) { + rule.setParamFlowItemList(new ArrayList()); + } + + Map itemMap = parseHotItems(rule.getParamFlowItemList()); + rule.setParsedHotItems(itemMap); + } + } + + static Map parseHotItems(List items) { + Map itemMap = new HashMap(); + if (items == null || items.isEmpty()) { + return itemMap; + } + for (ParamFlowItem item : items) { + // Value should not be null. + Object value; + try { + value = parseItemValue(item.getObject(), item.getClassType()); + } catch (Exception ex) { + RecordLog.warn("[ParamFlowRuleUtil] Failed to parse value for item: " + item, ex); + continue; + } + if (item.getCount() == null || item.getCount() < 0 || value == null) { + RecordLog.warn("[ParamFlowRuleUtil] Ignoring invalid exclusion parameter item: " + item); + continue; + } + itemMap.put(value, item.getCount()); + } + return itemMap; + } + + static Object parseItemValue(String value, String classType) { + if (value == null) { + throw new IllegalArgumentException("Null value"); + } + if (StringUtil.isBlank(classType)) { + // If the class type is not provided, then treat it as string. + return value; + } + // Handle primitive type. + if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) { + return Integer.parseInt(value); + } else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) { + return Boolean.parseBoolean(value); + } else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) { + return Long.parseLong(value); + } else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) { + return Double.parseDouble(value); + } else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) { + return Float.parseFloat(value); + } else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) { + return Byte.parseByte(value); + } else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) { + return Short.parseShort(value); + } else if (char.class.toString().equals(classType)) { + char[] array = value.toCharArray(); + return array.length > 0 ? array[0] : null; + } + + return value; + } + + private ParamFlowRuleUtil() {} +} diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java index 6f27a934..a6ae69c0 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java @@ -15,11 +15,9 @@ */ package com.alibaba.csp.sentinel.slots.block.flow.param; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; @@ -109,84 +107,4 @@ public class ParamFlowRuleManagerTest { assertTrue(allRules.contains(ruleC)); assertTrue(allRules.contains(ruleD)); } - - @Test - public void testParseHotParamExceptionItemsFailure() { - String valueB = "Sentinel"; - Integer valueC = 6; - char valueD = 6; - float valueE = 11.11f; - // Null object will not be parsed. - ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName()); - // Hot item with empty class type will be treated as string. - ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null); - ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, ""); - // Bad count will not be parsed. - ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5); - ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName()); - - List badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE); - Map parsedItems = ParamFlowRuleManager.parseHotItems(badItems); - - // Value B and E will be parsed, but ignoring the type. - assertEquals(2, parsedItems.size()); - assertEquals(itemB.getCount(), parsedItems.get(valueB)); - assertFalse(parsedItems.containsKey(valueE)); - assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE))); - } - - @Test - public void testParseHotParamExceptionItemsSuccess() { - // Test for empty list. - assertEquals(0, ParamFlowRuleManager.parseHotItems(null).size()); - assertEquals(0, ParamFlowRuleManager.parseHotItems(new ArrayList()).size()); - - // Test for boxing objects and primitive types. - Double valueA = 1.1d; - String valueB = "Sentinel"; - Integer valueC = 6; - char valueD = 'c'; - ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1); - ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3); - ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5); - ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD)) - .setClassType(char.class.getName()) - .setCount(7); - List items = Arrays.asList(itemA, itemB, itemC, itemD); - Map parsedItems = ParamFlowRuleManager.parseHotItems(items); - assertEquals(itemA.getCount(), parsedItems.get(valueA)); - assertEquals(itemB.getCount(), parsedItems.get(valueB)); - assertEquals(itemC.getCount(), parsedItems.get(valueC)); - assertEquals(itemD.getCount(), parsedItems.get(valueD)); - } - - @Test - public void testCheckValidHotParamRule() { - // Null or empty resource; - ParamFlowRule rule1 = new ParamFlowRule(); - ParamFlowRule rule2 = new ParamFlowRule(""); - assertFalse(ParamFlowRuleManager.isValidRule(null)); - assertFalse(ParamFlowRuleManager.isValidRule(rule1)); - assertFalse(ParamFlowRuleManager.isValidRule(rule2)); - - // Invalid threshold count. - ParamFlowRule rule3 = new ParamFlowRule("abc") - .setCount(-1) - .setParamIdx(1); - assertFalse(ParamFlowRuleManager.isValidRule(rule3)); - - // Parameter index not set or invalid. - ParamFlowRule rule4 = new ParamFlowRule("abc") - .setCount(1); - ParamFlowRule rule5 = new ParamFlowRule("abc") - .setCount(1) - .setParamIdx(-1); - assertFalse(ParamFlowRuleManager.isValidRule(rule4)); - assertFalse(ParamFlowRuleManager.isValidRule(rule5)); - - ParamFlowRule goodRule = new ParamFlowRule("abc") - .setCount(10) - .setParamIdx(1); - assertTrue(ParamFlowRuleManager.isValidRule(goodRule)); - } } \ No newline at end of file diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java new file mode 100644 index 00000000..af49f476 --- /dev/null +++ b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java @@ -0,0 +1,95 @@ +package com.alibaba.csp.sentinel.slots.block.flow.param; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Eric Zhao + */ +public class ParamFlowRuleUtilTest { + @Test + public void testCheckValidHotParamRule() { + // Null or empty resource; + ParamFlowRule rule1 = new ParamFlowRule(); + ParamFlowRule rule2 = new ParamFlowRule(""); + assertFalse(ParamFlowRuleUtil.isValidRule(null)); + assertFalse(ParamFlowRuleUtil.isValidRule(rule1)); + assertFalse(ParamFlowRuleUtil.isValidRule(rule2)); + + // Invalid threshold count. + ParamFlowRule rule3 = new ParamFlowRule("abc") + .setCount(-1) + .setParamIdx(1); + assertFalse(ParamFlowRuleUtil.isValidRule(rule3)); + + // Parameter index not set or invalid. + ParamFlowRule rule4 = new ParamFlowRule("abc") + .setCount(1); + ParamFlowRule rule5 = new ParamFlowRule("abc") + .setCount(1) + .setParamIdx(-1); + assertFalse(ParamFlowRuleUtil.isValidRule(rule4)); + assertFalse(ParamFlowRuleUtil.isValidRule(rule5)); + + ParamFlowRule goodRule = new ParamFlowRule("abc") + .setCount(10) + .setParamIdx(1); + assertTrue(ParamFlowRuleUtil.isValidRule(goodRule)); + } + + @Test + public void testParseHotParamExceptionItemsFailure() { + String valueB = "Sentinel"; + Integer valueC = 6; + char valueD = 6; + float valueE = 11.11f; + // Null object will not be parsed. + ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName()); + // Hot item with empty class type will be treated as string. + ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null); + ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, ""); + // Bad count will not be parsed. + ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5); + ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName()); + + List badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE); + Map parsedItems = ParamFlowRuleUtil.parseHotItems(badItems); + + // Value B and E will be parsed, but ignoring the type. + assertEquals(2, parsedItems.size()); + assertEquals(itemB.getCount(), parsedItems.get(valueB)); + assertFalse(parsedItems.containsKey(valueE)); + assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE))); + } + + @Test + public void testParseHotParamExceptionItemsSuccess() { + // Test for empty list. + assertEquals(0, ParamFlowRuleUtil.parseHotItems(null).size()); + assertEquals(0, ParamFlowRuleUtil.parseHotItems(new ArrayList()).size()); + + // Test for boxing objects and primitive types. + Double valueA = 1.1d; + String valueB = "Sentinel"; + Integer valueC = 6; + char valueD = 'c'; + ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1); + ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3); + ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5); + ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD)) + .setClassType(char.class.getName()) + .setCount(7); + List items = Arrays.asList(itemA, itemB, itemC, itemD); + Map parsedItems = ParamFlowRuleUtil.parseHotItems(items); + assertEquals(itemA.getCount(), parsedItems.get(valueA)); + assertEquals(itemB.getCount(), parsedItems.get(valueB)); + assertEquals(itemC.getCount(), parsedItems.get(valueC)); + assertEquals(itemD.getCount(), parsedItems.get(valueD)); + } +} \ No newline at end of file From b3ae7f58bf8c1878c9fceee3caf965bba59d8895 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Fri, 23 Nov 2018 17:53:36 +0800 Subject: [PATCH 04/20] Add common module for Sentinel default cluster implementation - Add a `ClusterTransportClient` for transport abstraction of Sentinel cluster - Add universal request/response interface and common ClusterRequest/ClusterResponse abstraction - Add common request/response data entity - Add basic abstraction of codec (EntityWriter and EntityDecoder) Signed-off-by: Eric Zhao --- .../sentinel/cluster/ClusterConstants.java | 44 ++++++++++ .../cluster/ClusterErrorMessages.java | 32 +++++++ .../cluster/ClusterTransportClient.java | 37 ++++++++ .../sentinel/cluster/codec/EntityDecoder.java | 33 +++++++ .../sentinel/cluster/codec/EntityWriter.java | 35 ++++++++ .../codec/request/RequestEntityDecoder.java | 27 ++++++ .../codec/request/RequestEntityWriter.java | 29 +++++++ .../codec/response/ResponseEntityDecoder.java | 27 ++++++ .../codec/response/ResponseEntityWriter.java | 29 +++++++ .../exception/SentinelClusterException.java | 32 +++++++ .../cluster/request/ClusterRequest.java | 79 +++++++++++++++++ .../csp/sentinel/cluster/request/Request.java | 39 +++++++++ .../cluster/request/data/FlowRequestData.java | 63 ++++++++++++++ .../request/data/ParamFlowRequestData.java | 65 ++++++++++++++ .../cluster/response/ClusterResponse.java | 87 +++++++++++++++++++ .../sentinel/cluster/response/Response.java | 46 ++++++++++ .../response/data/FlowTokenResponseData.java | 52 +++++++++++ .../alibaba/csp/sentinel/util/SpiLoader.java | 49 +++++++++++ 18 files changed, 805 insertions(+) create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java new file mode 100644 index 00000000..0183ab72 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterConstants { + + public static final int MSG_TYPE_PING = 0; + public static final int MSG_TYPE_FLOW = 1; + public static final int MSG_TYPE_PARAM_FLOW = 2; + + public static final int RESPONSE_STATUS_BAD = -1; + public static final int RESPONSE_STATUS_OK = 0; + + public static final int PARAM_TYPE_INTEGER = 0; + public static final int PARAM_TYPE_LONG = 1; + public static final int PARAM_TYPE_BYTE = 2; + public static final int PARAM_TYPE_DOUBLE = 3; + public static final int PARAM_TYPE_FLOAT = 4; + public static final int PARAM_TYPE_SHORT = 5; + public static final int PARAM_TYPE_BOOLEAN = 6; + public static final int PARAM_TYPE_STRING = 7; + + public static final int DEFAULT_CLUSTER_SERVER_PORT = 8730; + public static final int DEFAULT_REQUEST_TIMEOUT = 20; + + private ClusterConstants() {} +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java new file mode 100644 index 00000000..3e6ad416 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java @@ -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; + +/** + * @author jialiang.ljl + * @since 1.4.0 + */ +public final class ClusterErrorMessages { + + public static final String BAD_REQUEST = "bad request"; + public static final String UNEXPECTED_STATUS = "unexpected status"; + public static final String TOO_MANY_REQUESTS = "too many requests (client side)"; + public static final String REQUEST_TIME_OUT = "request time out"; + public static final String CLIENT_NOT_READY = "client not ready (not running or initializing)"; + public static final String NO_RULES_IN_SERVER = "no rules in token server"; + + private ClusterErrorMessages() {} +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java new file mode 100644 index 00000000..26af3c20 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java @@ -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; + +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; + +/** + * Synchronous transport client for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTransportClient { + + /** + * Send request to remote server and get response. + * + * @param request Sentinel cluster request + * @return response from remote server + * @throws Exception some error occurs + */ + ClusterResponse sendRequest(ClusterRequest request) throws Exception; +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java new file mode 100644 index 00000000..1bb830ac --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java @@ -0,0 +1,33 @@ +/* + * 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.codec; + +/** + * @param source stream type + * @param target entity type + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EntityDecoder { + + /** + * Decode target object from source stream. + * + * @param source source stream + * @return decoded target object + */ + T decode(S source); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java new file mode 100644 index 00000000..03dfcbaf --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java @@ -0,0 +1,35 @@ +/* + * 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.codec; + +/** + * A universal interface for publishing entities to a target stream. + * + * @param entity type + * @param target stream type + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EntityWriter { + + /** + * Write the provided entity to target stream. + * + * @param entity entity to publish + * @param target the target stream + */ + void writeTo(E entity, T target); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java new file mode 100644 index 00000000..8b9fd33f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java @@ -0,0 +1,27 @@ +/* + * 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.codec.request; + +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; +import com.alibaba.csp.sentinel.cluster.request.Request; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public interface RequestEntityDecoder extends EntityDecoder { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java new file mode 100644 index 00000000..814bbed1 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java @@ -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.codec.request; + +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.cluster.request.Request; + +/** + * A universal {@link EntityWriter} interface for publishing {@link Request} to a target stream. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface RequestEntityWriter extends EntityWriter { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java new file mode 100644 index 00000000..6bb03ae1 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java @@ -0,0 +1,27 @@ +/* + * 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.codec.response; + +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; +import com.alibaba.csp.sentinel.cluster.response.Response; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ResponseEntityDecoder extends EntityDecoder { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java new file mode 100644 index 00000000..89e5625c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java @@ -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.codec.response; + +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.cluster.response.Response; + +/** + * A universal {@link EntityWriter} interface for publishing {@link Response} to a target stream. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ResponseEntityWriter extends EntityWriter { + +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java new file mode 100644 index 00000000..ddc73095 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java @@ -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.exception; + +/** + * @author jialiang.ljl + * @since 1.4.0 + */ +public class SentinelClusterException extends Exception { + + public SentinelClusterException(String errorMsg) { + super(errorMsg); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java new file mode 100644 index 00000000..2e513e41 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.request; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterRequest implements Request { + + private int id; + private int type; + + private T data; + + public ClusterRequest() {} + + public ClusterRequest(int id, int type, T data) { + this.id = id; + this.type = type; + this.data = data; + } + + public ClusterRequest(int type, T data) { + this.type = type; + this.data = data; + } + + @Override + public int getId() { + return id; + } + + public ClusterRequest setId(int id) { + this.id = id; + return this; + } + + @Override + public int getType() { + return type; + } + + public ClusterRequest setType(int type) { + this.type = type; + return this; + } + + public T getData() { + return data; + } + + public ClusterRequest setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return "ClusterRequest{" + + "id=" + id + + ", type=" + type + + ", data=" + data + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java new file mode 100644 index 00000000..cda5b9c0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java @@ -0,0 +1,39 @@ +/* + * 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.request; + +/** + * Cluster transport request interface. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface Request { + + /** + * Get request type. + * + * @return request type + */ + int getType(); + + /** + * Get request ID. + * + * @return unique request ID + */ + int getId(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java new file mode 100644 index 00000000..ab401983 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java @@ -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.request.data; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowRequestData { + + private long flowId; + private int count; + private boolean priority; + + public long getFlowId() { + return flowId; + } + + public FlowRequestData setFlowId(long flowId) { + this.flowId = flowId; + return this; + } + + public int getCount() { + return count; + } + + public FlowRequestData setCount(int count) { + this.count = count; + return this; + } + + public boolean isPriority() { + return priority; + } + + public FlowRequestData setPriority(boolean priority) { + this.priority = priority; + return this; + } + + @Override + public String toString() { + return "FlowRequestData{" + + "flowId=" + flowId + + ", count=" + count + + ", priority=" + priority + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java new file mode 100644 index 00000000..c6ac6ec4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java @@ -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.request.data; + +import java.util.Collection; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ParamFlowRequestData { + + private long flowId; + private int count; + private Collection params; + + public long getFlowId() { + return flowId; + } + + public ParamFlowRequestData setFlowId(long flowId) { + this.flowId = flowId; + return this; + } + + public int getCount() { + return count; + } + + public ParamFlowRequestData setCount(int count) { + this.count = count; + return this; + } + + public Collection getParams() { + return params; + } + + public ParamFlowRequestData setParams(Collection params) { + this.params = params; + return this; + } + + @Override + public String toString() { + return "ParamFlowRequestData{" + + "flowId=" + flowId + + ", count=" + count + + ", params=" + params + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java new file mode 100644 index 00000000..4f5ebf5b --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java @@ -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.response; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterResponse implements Response { + + private int id; + private int type; + private int status; + + private T data; + + public ClusterResponse() {} + + public ClusterResponse(int id, int type, int status, T data) { + this.id = id; + this.type = type; + this.status = status; + this.data = data; + } + + @Override + public int getId() { + return id; + } + + public ClusterResponse setId(int id) { + this.id = id; + return this; + } + + @Override + public int getType() { + return type; + } + + public ClusterResponse setType(int type) { + this.type = type; + return this; + } + + @Override + public int getStatus() { + return status; + } + + public ClusterResponse setStatus(int status) { + this.status = status; + return this; + } + + public T getData() { + return data; + } + + public ClusterResponse setData(T data) { + this.data = data; + return this; + } + + @Override + public String toString() { + return "ClusterResponse{" + + "id=" + id + + ", type=" + type + + ", status=" + status + + ", data=" + data + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java new file mode 100644 index 00000000..037088d9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.response; + +/** + * Cluster transport response interface. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface Response { + + /** + * Get response ID. + * + * @return response ID + */ + int getId(); + + /** + * Get response type. + * + * @return response type + */ + int getType(); + + /** + * Get response status. + * + * @return response status + */ + int getStatus(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java new file mode 100644 index 00000000..2ca2842c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.response.data; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowTokenResponseData { + + private int remainingCount; + private int waitInMs; + + public int getRemainingCount() { + return remainingCount; + } + + public FlowTokenResponseData setRemainingCount(int remainingCount) { + this.remainingCount = remainingCount; + return this; + } + + public int getWaitInMs() { + return waitInMs; + } + + public FlowTokenResponseData setWaitInMs(int waitInMs) { + this.waitInMs = waitInMs; + return this; + } + + @Override + public String toString() { + return "FlowTokenResponseData{" + + "remainingCount=" + remainingCount + + ", waitInMs=" + waitInMs + + '}'; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java new file mode 100644 index 00000000..b057fc55 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java @@ -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.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class SpiLoader { + + private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap(); + + public static T loadFirstInstance(Class clazz) { + String key = clazz.getName(); + // Not thread-safe, as it's expected to be resolved in a thread-safe context. + ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key); + if (serviceLoader == null) { + serviceLoader = ServiceLoader.load(clazz); + SERVICE_LOADER_MAP.put(key, serviceLoader); + } + + Iterator iterator = serviceLoader.iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } else { + return null; + } + } + + private SpiLoader() {} +} From 6caa19ea95bb723752a6c6b5f746c910b965e118 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Fri, 23 Nov 2018 19:35:06 +0800 Subject: [PATCH 05/20] Add default Sentinel cluster transport implementation with Netty - Add Netty transport client implementation and default cluster token client - Add config manager for cluster client - Add codec SPI mechanism for client entity decoder / writer Signed-off-by: Eric Zhao --- .../sentinel-cluster-client-default/README.md | 1 + .../cluster/client/ClientConstants.java | 32 +++ .../client/DefaultClusterTokenClient.java | 105 +++++++++ .../cluster/client/NettyTransportClient.java | 208 ++++++++++++++++++ .../codec/ClientEntityCodecProvider.java | 62 ++++++ .../codec/DefaultRequestEntityWriter.java | 54 +++++ .../codec/DefaultResponseEntityDecoder.java | 66 ++++++ .../codec/data/FlowRequestDataWriter.java | 39 ++++ .../codec/data/FlowResponseDataDecoder.java | 39 ++++ .../data/ParamFlowRequestDataWriter.java | 128 +++++++++++ .../codec/netty/NettyRequestEncoder.java | 44 ++++ .../codec/netty/NettyResponseDecoder.java | 54 +++++ .../registry/RequestDataWriterRegistry.java | 47 ++++ .../registry/ResponseDataDecodeRegistry.java | 48 ++++ .../client/config/ClusterClientConfig.java | 65 ++++++ .../config/ClusterClientConfigManager.java | 30 +++ .../client/handler/TokenClientHandler.java | 72 ++++++ .../handler/TokenClientPromiseHolder.java | 60 +++++ ....cluster.codec.request.RequestEntityWriter | 1 + ...uster.codec.response.ResponseEntityDecoder | 1 + 20 files changed, 1156 insertions(+) create mode 100644 sentinel-cluster/sentinel-cluster-client-default/README.md create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder diff --git a/sentinel-cluster/sentinel-cluster-client-default/README.md b/sentinel-cluster/sentinel-cluster-client-default/README.md new file mode 100644 index 00000000..d09d8263 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/README.md @@ -0,0 +1 @@ +# Sentinel Cluster Client (Default) \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java new file mode 100644 index 00000000..9645feb5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java @@ -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.client; + +/** + * @author Eric Zhao + */ +public final class ClientConstants { + + public static final int TYPE_PING = 0; + public static final int TYPE_FLOW = 1; + public static final int TYPE_PARAM_FLOW = 2; + + public static final int CLIENT_STATUS_OFF = 0; + public static final int CLIENT_STATUS_PENDING = 1; + public static final int CLIENT_STATUS_STARTED = 2; + + private ClientConstants() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java new file mode 100644 index 00000000..668f43bd --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -0,0 +1,105 @@ +/* + * 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.client; + +import java.util.Collection; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.log.ClusterStatLogUtil; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; +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; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultClusterTokenClient implements ClusterTokenClient { + + private ClusterTransportClient transportClient; + + public DefaultClusterTokenClient() { + // TODO: load and create transport client here. + } + + public DefaultClusterTokenClient(ClusterTransportClient transportClient) { + this.transportClient = transportClient; + } + + @Override + public TokenServerDescriptor currentServer() { + return new TokenServerDescriptor(); + } + + @Override + public TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) { + if (notValidRequest(flowId, acquireCount)) { + return badRequest(); + } + FlowRequestData data = new FlowRequestData().setCount(acquireCount) + .setFlowId(flowId).setPriority(prioritized); + ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data); + try { + return sendTokenRequest(request); + } catch (Exception ex) { + ClusterStatLogUtil.log(ex.getMessage()); + return new TokenResult(TokenResultStatus.FAIL); + } + } + + @Override + public TokenResult requestParamToken(Long flowId, int acquireCount, Collection params) { + if (notValidRequest(flowId, acquireCount) || params == null || params.isEmpty()) { + return badRequest(); + } + ParamFlowRequestData data = new ParamFlowRequestData().setCount(acquireCount) + .setFlowId(flowId).setParams(params); + ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_PARAM_FLOW, data); + try { + return sendTokenRequest(request); + } catch (Exception ex) { + ClusterStatLogUtil.log(ex.getMessage()); + return new TokenResult(TokenResultStatus.FAIL); + } + } + + private boolean notValidRequest(Long id, int count) { + return id == null || id <= 0 || count <= 0; + } + + private TokenResult badRequest() { + return new TokenResult(TokenResultStatus.BAD_REQUEST); + } + + private TokenResult sendTokenRequest(ClusterRequest request) throws Exception { + ClusterResponse response = transportClient.sendRequest(request); + TokenResult result = new TokenResult(response.getStatus()); + if (response.getData() != null) { + FlowTokenResponseData responseData = (FlowTokenResponseData)response.getData(); + result.setRemaining(responseData.getRemainingCount()) + .setWaitInMs(responseData.getWaitInMs()); + } + return result; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java new file mode 100644 index 00000000..676f4d44 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java @@ -0,0 +1,208 @@ +/* + * 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.client; + +import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.atomic.AtomicInteger; + +import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages; +import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; +import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder; +import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler; +import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder; +import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.request.Request; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.AssertUtil; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.util.concurrent.GenericFutureListener; + +/** + * Netty transport client implementation for Sentinel cluster transport. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class NettyTransportClient implements ClusterTransportClient { + + private ClusterClientConfig clientConfig; + private String host; + private int port; + + private Channel channel; + private NioEventLoopGroup eventLoopGroup; + private TokenClientHandler clientHandler; + + private AtomicInteger idGenerator = new AtomicInteger(0); + + private AtomicInteger failConnectedTime = new AtomicInteger(0); + + public NettyTransportClient(ClusterClientConfig clientConfig, String host, int port) { + this.clientConfig = clientConfig; + this.host = host; + this.port = port; + } + + private Bootstrap initClientBootstrap() { + Bootstrap b = new Bootstrap(); + eventLoopGroup = new NioEventLoopGroup(); + b.group(eventLoopGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_TIMEOUT, 20) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + clientHandler = new TokenClientHandler(); + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); + pipeline.addLast(new NettyResponseDecoder()); + pipeline.addLast(new LengthFieldPrepender(2)); + pipeline.addLast(new NettyRequestEncoder()); + pipeline.addLast(clientHandler); + } + }); + + return b; + } + + private void connect(Bootstrap b) { + b.connect(host, port).addListener(new GenericFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (future.cause() != null) { + RecordLog.warn( + "[NettyTransportClient] Could not connect after " + failConnectedTime.get() + " times", + future.cause()); + failConnectedTime.incrementAndGet(); + channel = null; + } else { + failConnectedTime.set(0); + channel = future.channel(); + RecordLog.info("[NettyTransportClient] Successfully connect to server " + host + ":" + port); + } + } + }); + } + + public void start() { + connect(initClientBootstrap()); + } + + public void stop() { + if (channel != null) { + channel.close(); + channel = null; + } + if (eventLoopGroup != null) { + eventLoopGroup.shutdownGracefully(); + } + failConnectedTime.set(0); + + RecordLog.info("[NettyTransportClient] Token client stopped"); + } + + private boolean validRequest(Request request) { + return request != null && request.getType() >= 0; + } + + public boolean isReady() { + return channel != null && clientHandler != null && clientHandler.hasStarted(); + } + + @Override + public ClusterResponse sendRequest(ClusterRequest request) throws Exception { + if (!isReady()) { + throw new SentinelClusterException(ClusterErrorMessages.CLIENT_NOT_READY); + } + if (!validRequest(request)) { + throw new SentinelClusterException(ClusterErrorMessages.BAD_REQUEST); + } + int xid = getCurrentId(); + try { + request.setId(xid); + + channel.writeAndFlush(request); + + ChannelPromise promise = channel.newPromise(); + TokenClientPromiseHolder.putPromise(xid, promise); + + // TODO: timeout + if (!promise.await(20)) { + throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT); + } + + SimpleEntry entry = TokenClientPromiseHolder.getEntry(xid); + if (entry == null || entry.getValue() == null) { + // Should not go through here. + throw new SentinelClusterException(ClusterErrorMessages.UNEXPECTED_STATUS); + } + return entry.getValue(); + } finally { + TokenClientPromiseHolder.remove(xid); + } + } + + private int getCurrentId() { + if (idGenerator.get() > MAX_ID) { + idGenerator.set(0); + } + return idGenerator.incrementAndGet(); + } + + /*public CompletableFuture sendRequestAsync(ClusterRequest request) { + // Uncomment this when min target JDK is 1.8. + if (!validRequest(request)) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Bad request")); + } + int xid = getCurrentId(); + request.setId(xid); + + CompletableFuture future = new CompletableFuture<>(); + channel.writeAndFlush(request) + .addListener(f -> { + if (f.isSuccess()) { + future.complete(someResult); + } else if (f.cause() != null) { + future.completeExceptionally(f.cause()); + } else { + future.cancel(false); + } + }); + return future; + }*/ + + private static final int MAX_ID = 999_999_999; +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java new file mode 100644 index 00000000..6cb0ebda --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java @@ -0,0 +1,62 @@ +/* + * 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.client.codec; + +import com.alibaba.csp.sentinel.util.SpiLoader; +import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; +import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClientEntityCodecProvider { + + private static RequestEntityWriter requestEntityWriter = null; + private static ResponseEntityDecoder responseEntityDecoder = null; + + static { + resolveInstance(); + } + + private static void resolveInstance() { + RequestEntityWriter writer = SpiLoader.loadFirstInstance(RequestEntityWriter.class); + if (writer == null) { + RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed"); + } else { + requestEntityWriter = writer; + RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: " + requestEntityWriter.getClass().getCanonicalName()); + } + ResponseEntityDecoder decoder = SpiLoader.loadFirstInstance(ResponseEntityDecoder.class); + if (decoder == null) { + RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed"); + } else { + responseEntityDecoder = decoder; + RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: " + responseEntityDecoder.getClass().getCanonicalName()); + } + } + + public static RequestEntityWriter getRequestEntityWriter() { + return requestEntityWriter; + } + + public static ResponseEntityDecoder getResponseEntityDecoder() { + return responseEntityDecoder; + } + + private ClientEntityCodecProvider() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java new file mode 100644 index 00000000..c4a3ce91 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.codec; + +import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry; +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.request.Request; +import com.alibaba.csp.sentinel.log.RecordLog; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultRequestEntityWriter implements RequestEntityWriter { + + @Override + public void writeTo(ClusterRequest request, ByteBuf target) { + int type = request.getType(); + EntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type); + + if (requestDataWriter == null) { + // TODO: may need to throw exception? + RecordLog.warn( + "[NettyRequestEncoder] Cannot find matching request writer for type <{0}>, dropping the request", type); + return; + } + // Write head part of request. + writeHead(request, target); + // Write data part. + requestDataWriter.writeTo(request.getData(), target); + } + + private void writeHead(Request request, ByteBuf out) { + out.writeInt(request.getId()); + out.writeByte(request.getType()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java new file mode 100644 index 00000000..4c1ad34e --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java @@ -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.client.codec; + +import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; +import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.log.RecordLog; + +import io.netty.buffer.ByteBuf; + +/** + *

    Default entity decoder for any {@link ClusterResponse} entity.

    + * + *

    Decode format:

    + *
    + * +--------+---------+-----------+---------+
    + * | xid(4) | type(1) | status(1) | data... |
    + * +--------+---------+-----------+---------+
    + * 
    + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultResponseEntityDecoder implements ResponseEntityDecoder { + + @Override + public ClusterResponse decode(ByteBuf source) { + if (source.readableBytes() >= 6) { + int xid = source.readInt(); + int type = source.readByte(); + int status = source.readByte(); + + EntityDecoder decoder = ResponseDataDecodeRegistry.getDecoder(type); + if (decoder == null) { + RecordLog.warn("Unknown type of response data decoder: {0}", type); + return null; + } + + Object data; + if (source.readableBytes() == 0) { + data = null; + } else { + // TODO: handle decode error here. + data = decoder.decode(source); + } + + return new ClusterResponse<>(xid, type, status, data); + } + return null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java new file mode 100644 index 00000000..2683a925 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java @@ -0,0 +1,39 @@ +/* + * 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.client.codec.data; + +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; + +import io.netty.buffer.ByteBuf; + +/** + * +-------------------+--------------+----------------+---------------+------------------+ + * | RequestID(4 byte) | Type(1 byte) | FlowID(4 byte) | Count(4 byte) | PriorityFlag (1) | + * +-------------------+--------------+----------------+---------------+------------------+ + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowRequestDataWriter implements EntityWriter { + + @Override + public void writeTo(FlowRequestData entity, ByteBuf target) { + target.writeLong(entity.getFlowId()); + target.writeInt(entity.getCount()); + target.writeBoolean(entity.isPriority()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java new file mode 100644 index 00000000..c15355fe --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java @@ -0,0 +1,39 @@ +/* + * 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.client.codec.data; + +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; +import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowResponseDataDecoder implements EntityDecoder { + + @Override + public FlowTokenResponseData decode(ByteBuf source) { + FlowTokenResponseData data = new FlowTokenResponseData(); + + if (source.readableBytes() == 8) { + data.setRemainingCount(source.readInt()); + data.setWaitInMs(source.readInt()); + } + return data; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java new file mode 100644 index 00000000..7367b6e2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java @@ -0,0 +1,128 @@ +/* + * 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.client.codec.data; + +import java.util.Collection; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ParamFlowRequestDataWriter implements EntityWriter { + + @Override + public void writeTo(ParamFlowRequestData entity, ByteBuf target) { + target.writeLong(entity.getFlowId()); + target.writeInt(entity.getCount()); + + Collection params = entity.getParams(); + + // Write parameter amount. + int amount = calculateParamAmount(params); + target.writeInt(amount); + + // Serialize parameters with type flag. + for (Object param : entity.getParams()) { + encodeValue(param, target); + } + } + + private void encodeValue(Object param, ByteBuf target) { + // Handle primitive type. + if (param instanceof Integer || int.class.isInstance(param)) { + target.writeByte(ClusterConstants.PARAM_TYPE_INTEGER); + target.writeInt((Integer)param); + } else if (param instanceof String) { + encodeString((String)param, target); + } else if (boolean.class.isInstance(param) || param instanceof Boolean) { + target.writeByte(ClusterConstants.PARAM_TYPE_BOOLEAN); + target.writeBoolean((Boolean)param); + } else if (long.class.isInstance(param) || param instanceof Long) { + target.writeByte(ClusterConstants.PARAM_TYPE_LONG); + target.writeLong((Long)param); + } else if (double.class.isInstance(param) || param instanceof Double) { + target.writeByte(ClusterConstants.PARAM_TYPE_DOUBLE); + target.writeDouble((Double)param); + } else if (float.class.isInstance(param) || param instanceof Float) { + target.writeByte(ClusterConstants.PARAM_TYPE_FLOAT); + target.writeFloat((Float)param); + } else if (byte.class.isInstance(param) || param instanceof Byte) { + target.writeByte(ClusterConstants.PARAM_TYPE_BYTE); + target.writeByte((Byte)param); + } else if (short.class.isInstance(param) || param instanceof Short) { + target.writeByte(ClusterConstants.PARAM_TYPE_SHORT); + target.writeShort((Short)param); + } else { + // Unexpected type, drop. + } + } + + private void encodeString(String param, ByteBuf target) { + target.writeByte(ClusterConstants.PARAM_TYPE_STRING); + byte[] tmpChars = param.getBytes(); + target.writeInt(tmpChars.length); + target.writeBytes(tmpChars); + } + + private int calculateParamAmount(/*@NonEmpty*/ Collection params) { + int size = 0; + int length = 0; + for (Object param : params) { + int s = calculateParamTransportSize(param); + if (size + s > PARAM_MAX_SIZE) { + break; + } + length++; + } + return length; + } + + private int calculateParamTransportSize(Object value) { + // Layout for primitives: |type flag(1)|value| + // size = original size + type flag (1) + if (value instanceof Integer || int.class.isInstance(value)) { + return 5; + } else if (value instanceof String) { + // Layout for string: |type flag(1)|length(4)|string content| + String tmpValue = (String)value; + byte[] tmpChars = tmpValue.getBytes(); + return 1 + 4 + tmpChars.length; + } else if (boolean.class.isInstance(value) || value instanceof Boolean) { + return 2; + } else if (long.class.isInstance(value) || value instanceof Long) { + return 9; + } else if (double.class.isInstance(value) || value instanceof Double) { + return 9; + } else if (float.class.isInstance(value) || value instanceof Float) { + return 5; + } else if (byte.class.isInstance(value) || value instanceof Byte) { + return 2; + } else if (short.class.isInstance(value) || value instanceof Short) { + return 3; + } else { + // Ignore unexpected type. + return 0; + } + } + + private static final int PARAM_MAX_SIZE = 1000; +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java new file mode 100644 index 00000000..bc8b05cb --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.codec.netty; + +import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider; +import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; +import com.alibaba.csp.sentinel.cluster.request.Request; +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 + */ +public class NettyRequestEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception { + RequestEntityWriter requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter(); + if (requestEntityWriter == null) { + // TODO: may need to throw exception? + RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request"); + return; + } + + requestEntityWriter.writeTo(request, out); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java new file mode 100644 index 00000000..75c15a72 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.codec.netty; + +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider; +import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; +import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.cluster.response.Response; +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 NettyResponseDecoder extends ByteToMessageDecoder { + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + ResponseEntityDecoder responseDecoder = ClientEntityCodecProvider.getResponseEntityDecoder(); + if (responseDecoder == null) { + // TODO: may need to throw exception? + RecordLog.warn("[NettyResponseDecoder] Cannot resolve the global response entity decoder, " + + "dropping the response"); + return; + } + + // TODO: handle decode error here. + Response response = responseDecoder.decode(in); + if (response != null) { + out.add(response); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java new file mode 100644 index 00000000..4ad9305d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.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 + */ +public final class RequestDataWriterRegistry { + + private static final Map> WRITER_MAP = new HashMap<>(); + + public static boolean addWriter(int type, EntityWriter writer) { + if (WRITER_MAP.containsKey(type)) { + return false; + } + WRITER_MAP.put(type, (EntityWriter)writer); + return true; + } + + public static EntityWriter getWriter(int type) { + return WRITER_MAP.get(type); + } + + public static boolean remove(int type) { + return WRITER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java new file mode 100644 index 00000000..543fa774 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java @@ -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.client.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 ResponseDataDecodeRegistry { + + private static final Map> DECODER_MAP = new HashMap<>(); + + public static boolean addDecoder(int type, EntityDecoder decoder) { + if (DECODER_MAP.containsKey(type)) { + return false; + } + DECODER_MAP.put(type, decoder); + return true; + } + + public static EntityDecoder getDecoder(int type) { + return (EntityDecoder)DECODER_MAP.get(type); + } + + public static boolean removeDecoder(int type) { + return DECODER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java new file mode 100644 index 00000000..81a0edf0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java @@ -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.client.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterClientConfig { + + private String serverHost; + private int serverPort; + + private int requestTimeout; + private int connectTimeout; + + public String getServerHost() { + return serverHost; + } + + public ClusterClientConfig setServerHost(String serverHost) { + this.serverHost = serverHost; + return this; + } + + public int getServerPort() { + return serverPort; + } + + public ClusterClientConfig setServerPort(int serverPort) { + this.serverPort = serverPort; + return this; + } + + public int getRequestTimeout() { + return requestTimeout; + } + + public ClusterClientConfig setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; + return this; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public ClusterClientConfig setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java new file mode 100644 index 00000000..94ade2d9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.config; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; + +/** + * @author Eric Zhao + */ +public final class ClusterClientConfigManager { + + private static volatile String serverIp; + private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT; + private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT; + + private ClusterClientConfigManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java new file mode 100644 index 00000000..fc14cd35 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -0,0 +1,72 @@ +/* + * 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.client.handler; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.alibaba.csp.sentinel.cluster.client.ClientConstants; +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenClientHandler extends ChannelInboundHandlerAdapter { + + private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + currentState.set(ClientConstants.CLIENT_STATUS_STARTED); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println(String.format("[%s] Client message recv: %s", System.currentTimeMillis(), msg)); + if (msg instanceof ClusterResponse) { + ClusterResponse response = (ClusterResponse) msg; + + TokenClientPromiseHolder.completePromise(response.getId(), response); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + cause.printStackTrace(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + currentState.set(ClientConstants.CLIENT_STATUS_OFF); + } + + public int getCurrentState() { + return currentState.get(); + } + + public boolean hasStarted() { + return getCurrentState() == ClientConstants.CLIENT_STATUS_STARTED; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java new file mode 100644 index 00000000..033d5900 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java @@ -0,0 +1,60 @@ +/* + * 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.client.handler; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; + +import io.netty.channel.ChannelPromise; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenClientPromiseHolder { + + private static final Map> PROMISE_MAP = new ConcurrentHashMap<>(); + + public static void putPromise(int xid, ChannelPromise promise) { + PROMISE_MAP.put(xid, new SimpleEntry(promise, null)); + } + + public static SimpleEntry getEntry(int xid) { + return PROMISE_MAP.get(xid); + } + + public static void remove(int xid) { + PROMISE_MAP.remove(xid); + } + + public static boolean completePromise(int xid, ClusterResponse response) { + if (!PROMISE_MAP.containsKey(xid)) { + return false; + } + ChannelPromise promise = PROMISE_MAP.get(xid).getKey(); + if (promise.isDone() || promise.isCancelled()) { + return false; + } + PROMISE_MAP.get(xid).setValue(response); + promise.setSuccess(); + return true; + } + + private TokenClientPromiseHolder() {} +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter new file mode 100644 index 00000000..23698745 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.codec.DefaultRequestEntityWriter \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder new file mode 100644 index 00000000..cd43e7e2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.codec.DefaultResponseEntityDecoder \ No newline at end of file From f83ea428ff2f72f485f18733e9c8239b6938e6d9 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Tue, 27 Nov 2018 13:07:20 +0800 Subject: [PATCH 06/20] Update token client interface and default implementation Signed-off-by: Eric Zhao --- .../sentinel-cluster-client-default/pom.xml | 16 +++ .../client/DefaultClusterTokenClient.java | 88 ++++++++++++-- .../cluster/client/NettyTransportClient.java | 28 +++-- .../codec/DefaultRequestEntityWriter.java | 4 +- .../codec/netty/NettyRequestEncoder.java | 1 + .../client/config/ClusterClientConfig.java | 10 ++ .../config/ClusterClientConfigManager.java | 111 +++++++++++++++++- .../client/config/ServerChangeObserver.java | 30 +++++ .../client/handler/TokenClientHandler.java | 8 +- .../ModifyClusterClientConfigHandler.java | 42 +++++++ ...ba.csp.sentinel.cluster.ClusterTokenClient | 1 + ...libaba.csp.sentinel.command.CommandHandler | 1 + .../cluster/ClusterTransportClient.java | 21 ++++ .../sentinel/cluster/ClusterTokenClient.java | 2 +- .../cluster/TokenServerDescriptor.java | 17 +-- .../alibaba/csp/sentinel/util/AssertUtil.java | 6 + 16 files changed, 345 insertions(+), 41 deletions(-) create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient create mode 100755 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler diff --git a/sentinel-cluster/sentinel-cluster-client-default/pom.xml b/sentinel-cluster/sentinel-cluster-client-default/pom.xml index 42b89d43..de189630 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-client-default/pom.xml @@ -17,6 +17,11 @@ com.alibaba.csp sentinel-core + + com.alibaba.csp + sentinel-transport-common + provided + com.alibaba.csp sentinel-cluster-common-default @@ -26,5 +31,16 @@ io.netty netty-all + + + junit + junit + test + + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java index 668f43bd..1d7bc646 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -24,32 +24,92 @@ import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; +import com.alibaba.csp.sentinel.cluster.client.config.ServerChangeObserver; import com.alibaba.csp.sentinel.cluster.log.ClusterStatLogUtil; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; 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.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; /** + * Default implementation of {@link ClusterTokenClient}. + * * @author Eric Zhao * @since 1.4.0 */ public class DefaultClusterTokenClient implements ClusterTokenClient { private ClusterTransportClient transportClient; + private TokenServerDescriptor serverDescriptor; public DefaultClusterTokenClient() { - // TODO: load and create transport client here. + ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() { + @Override + public void onRemoteServerChange(ClusterClientConfig clusterClientConfig) { + changeServer(clusterClientConfig); + } + }); + initNewConnection(); } public DefaultClusterTokenClient(ClusterTransportClient transportClient) { + // TODO: only for test, remove this constructor. this.transportClient = transportClient; } + private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientConfig config) { + if (descriptor == null || config == null) { + return false; + } + return descriptor.getHost().equals(config.getServerHost()) && descriptor.getPort() == config.getServerPort(); + } + + private void initNewConnection() { + if (transportClient != null) { + return; + } + String host = ClusterClientConfigManager.getServerHost(); + int port = ClusterClientConfigManager.getServerPort(); + if (StringUtil.isBlank(host) || port <= 0) { + return; + } + + try { + this.transportClient = new NettyTransportClient(host, port); + this.serverDescriptor = new TokenServerDescriptor(host, port); + transportClient.start(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private void changeServer(/*@Valid*/ ClusterClientConfig config) { + if (serverEqual(serverDescriptor, config)) { + return; + } + try { + // TODO: what if the client is pending init? + if (transportClient != null && transportClient.isReady()) { + transportClient.stop(); + } + // Replace with new, even if the new client is not ready. + this.transportClient = new NettyTransportClient(config); + this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort()); + transportClient.start(); + RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor); + } catch (Exception ex) { + RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex); + ex.printStackTrace(); + } + } + @Override public TokenServerDescriptor currentServer() { - return new TokenServerDescriptor(); + return serverDescriptor; } @Override @@ -84,15 +144,11 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { } } - private boolean notValidRequest(Long id, int count) { - return id == null || id <= 0 || count <= 0; - } - - private TokenResult badRequest() { - return new TokenResult(TokenResultStatus.BAD_REQUEST); - } - private TokenResult sendTokenRequest(ClusterRequest request) throws Exception { + if (transportClient == null) { + RecordLog.warn("[DefaultClusterTokenClient] Client not created, please check your config for cluster client"); + return clientFail(); + } ClusterResponse response = transportClient.sendRequest(request); TokenResult result = new TokenResult(response.getStatus()); if (response.getData() != null) { @@ -102,4 +158,16 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { } return result; } + + private boolean notValidRequest(Long id, int count) { + return id == null || id <= 0 || count <= 0; + } + + private TokenResult badRequest() { + return new TokenResult(TokenResultStatus.BAD_REQUEST); + } + + private TokenResult clientFail() { + return new TokenResult(TokenResultStatus.FAIL); + } } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java index 676f4d44..6fd21379 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java @@ -23,6 +23,7 @@ import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder; import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler; import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder; import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException; @@ -55,20 +56,25 @@ import io.netty.util.concurrent.GenericFutureListener; */ public class NettyTransportClient implements ClusterTransportClient { - private ClusterClientConfig clientConfig; - private String host; - private int port; + private final String host; + private final int port; private Channel channel; private NioEventLoopGroup eventLoopGroup; private TokenClientHandler clientHandler; private AtomicInteger idGenerator = new AtomicInteger(0); - private AtomicInteger failConnectedTime = new AtomicInteger(0); - public NettyTransportClient(ClusterClientConfig clientConfig, String host, int port) { - this.clientConfig = clientConfig; + public NettyTransportClient(ClusterClientConfig clientConfig) { + AssertUtil.notNull(clientConfig, "client config cannot be null"); + this.host = clientConfig.getServerHost(); + this.port = clientConfig.getServerPort(); + } + + public NettyTransportClient(String host, int port) { + AssertUtil.assertNotBlank(host, "remote host cannot be blank"); + AssertUtil.isTrue(port > 0, "port should be positive"); this.host = host; this.port = port; } @@ -117,11 +123,13 @@ public class NettyTransportClient implements ClusterTransportClient { }); } - public void start() { + @Override + public void start() throws Exception { connect(initClientBootstrap()); } - public void stop() { + @Override + public void stop() throws Exception { if (channel != null) { channel.close(); channel = null; @@ -138,6 +146,7 @@ public class NettyTransportClient implements ClusterTransportClient { return request != null && request.getType() >= 0; } + @Override public boolean isReady() { return channel != null && clientHandler != null && clientHandler.hasStarted(); } @@ -159,8 +168,7 @@ public class NettyTransportClient implements ClusterTransportClient { ChannelPromise promise = channel.newPromise(); TokenClientPromiseHolder.putPromise(xid, promise); - // TODO: timeout - if (!promise.await(20)) { + if (!promise.await(ClusterClientConfigManager.getRequestTimeout())) { throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT); } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java index c4a3ce91..f3dad331 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java @@ -37,8 +37,8 @@ public class DefaultRequestEntityWriter implements RequestEntityWriter, dropping the request", type); + RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{0}>," + + " dropping the request", type); return; } // Write head part of request. diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java index bc8b05cb..50a0476e 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java @@ -27,6 +27,7 @@ import io.netty.handler.codec.MessageToByteEncoder; /** * @author Eric Zhao + * @since 1.4.0 */ public class NettyRequestEncoder extends MessageToByteEncoder { diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java index 81a0edf0..87f048e9 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java @@ -62,4 +62,14 @@ public class ClusterClientConfig { this.connectTimeout = connectTimeout; return this; } + + @Override + public String toString() { + return "ClusterClientConfig{" + + "serverHost='" + serverHost + '\'' + + ", serverPort=" + serverPort + + ", requestTimeout=" + requestTimeout + + ", connectTimeout=" + connectTimeout + + '}'; + } } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java index 94ade2d9..834bdcd7 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java @@ -15,16 +15,125 @@ */ package com.alibaba.csp.sentinel.cluster.client.config; +import java.util.ArrayList; +import java.util.List; + import com.alibaba.csp.sentinel.cluster.ClusterConstants; +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.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; /** * @author Eric Zhao + * @since 1.4.0 */ public final class ClusterClientConfigManager { - private static volatile String serverIp; + /** + * Client config properties. + */ + private static volatile String serverHost = null; private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT; private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT; + private static final PropertyListener PROPERTY_LISTENER = new ClientConfigPropertyListener(); + private static SentinelProperty currentProperty = new DynamicSentinelProperty<>(); + + private static final List SERVER_CHANGE_OBSERVERS = new ArrayList<>(); + + static { + currentProperty.addListener(PROPERTY_LISTENER); + } + + public static void register2Property(SentinelProperty property) { + synchronized (PROPERTY_LISTENER) { + RecordLog.info("[ClusterClientConfigManager] Registering new property to cluster client config manager"); + currentProperty.removeListener(PROPERTY_LISTENER); + property.addListener(PROPERTY_LISTENER); + currentProperty = property; + } + } + + public static void addServerChangeObserver(ServerChangeObserver observer) { + AssertUtil.notNull(observer, "observer cannot be null"); + SERVER_CHANGE_OBSERVERS.add(observer); + } + + /** + * Apply new {@link ClusterClientConfig}, while the former config will be replaced. + * + * @param config new config to apply + */ + public static void applyNewConfig(ClusterClientConfig config) { + currentProperty.updateValue(config); + } + + private static class ClientConfigPropertyListener implements PropertyListener { + + @Override + public void configUpdate(ClusterClientConfig config) { + applyConfig(config); + } + + @Override + public void configLoad(ClusterClientConfig config) { + if (config == null) { + RecordLog.warn("[ClusterClientConfigManager] Empty initial config"); + return; + } + applyConfig(config); + } + + private synchronized void applyConfig(ClusterClientConfig config) { + if (!isValidConfig(config)) { + RecordLog.warn( + "[ClusterClientConfigManager] Invalid cluster client config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterClientConfigManager] Updating new config: " + config); + if (config.getRequestTimeout() != requestTimeout) { + requestTimeout = config.getRequestTimeout(); + } + updateServer(config); + } + } + + public static boolean isValidConfig(ClusterClientConfig config) { + return config != null && StringUtil.isNotBlank(config.getServerHost()) + && config.getServerPort() > 0 + && config.getRequestTimeout() > 0; + } + + public static void updateServer(ClusterClientConfig config) { + String host = config.getServerHost(); + int port = config.getServerPort(); + AssertUtil.assertNotBlank(host, "token server host cannot be empty"); + AssertUtil.isTrue(port > 0, "token server port should be valid (positive)"); + if (serverPort == port && host.equals(serverHost)) { + return; + } + for (ServerChangeObserver observer : SERVER_CHANGE_OBSERVERS) { + observer.onRemoteServerChange(config); + } + + serverHost = host; + serverPort = port; + } + + public static String getServerHost() { + return serverHost; + } + + public static int getServerPort() { + return serverPort; + } + + public static int getRequestTimeout() { + return requestTimeout; + } + private ClusterClientConfigManager() {} } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java new file mode 100644 index 00000000..30908476 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ServerChangeObserver { + + /** + * Callback on remote server address change. + * + * @param clusterClientConfig new cluster client config + */ + void onRemoteServerChange(ClusterClientConfig clusterClientConfig); +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java index fc14cd35..564b9bb5 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.client.ClientConstants; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; +import com.alibaba.csp.sentinel.log.RecordLog; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -34,6 +35,7 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { currentState.set(ClientConstants.CLIENT_STATUS_STARTED); + RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress()); } @Override @@ -48,13 +50,13 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - super.exceptionCaught(ctx, cause); - cause.printStackTrace(); + // TODO: should close the connection when an exception is raised. + RecordLog.warn("[TokenClientHandler] Client exception caught", cause); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - super.channelInactive(ctx); + RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: " + ctx.channel().remoteAddress()); } @Override diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java new file mode 100644 index 00000000..5cc5b4f8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java @@ -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.command.handler; + +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; +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; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "modifyClusterConfig") +public class ModifyClusterClientConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + + // TODO: parse the new config; + ClusterClientConfig clusterClientConfig = null; + ClusterClientConfigManager.applyNewConfig(clusterClientConfig); + + return CommandResponse.ofSuccess("ok"); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient new file mode 100644 index 00000000..3070db6a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler new file mode 100755 index 00000000..a0af73b5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java index 26af3c20..23ba9014 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java @@ -26,6 +26,20 @@ import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; */ public interface ClusterTransportClient { + /** + * Start the client. + * + * @throws Exception some error occurred (e.g. initialization failed) + */ + void start() throws Exception; + + /** + * Stop the client. + * + * @throws Exception some error occurred (e.g. shutdown failed) + */ + void stop() throws Exception; + /** * Send request to remote server and get response. * @@ -34,4 +48,11 @@ public interface ClusterTransportClient { * @throws Exception some error occurs */ ClusterResponse sendRequest(ClusterRequest request) throws Exception; + + /** + * Check whether the client has been started and ready for sending requests. + * + * @return true if the client is ready to send requests, otherwise false + */ + boolean isReady(); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java index 6a230c07..c0243974 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java @@ -26,7 +26,7 @@ public interface ClusterTokenClient extends TokenService { /** * Get descriptor of current token server. * - * @return current token server + * @return current token server if connected, otherwise null */ TokenServerDescriptor currentServer(); } \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java index 98739179..98636346 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java @@ -23,11 +23,10 @@ package com.alibaba.csp.sentinel.cluster; */ public class TokenServerDescriptor { - private String host; - private int port; - private String type; + private final String host; + private final int port; - public TokenServerDescriptor() {} + private String type = "default"; public TokenServerDescriptor(String host, int port) { this.host = host; @@ -38,20 +37,10 @@ public class TokenServerDescriptor { return host; } - public TokenServerDescriptor setHost(String host) { - this.host = host; - return this; - } - public int getPort() { return port; } - public TokenServerDescriptor setPort(int port) { - this.port = port; - return this; - } - public String getType() { return type; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java index 46dcc11e..4c67b738 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java @@ -28,6 +28,12 @@ public class AssertUtil { } } + public static void assertNotBlank(String string, String message) { + if (StringUtil.isBlank(string)) { + throw new IllegalArgumentException(message); + } + } + public static void notNull(Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); From b973aca1f59eed184df1162560e05a0c960a0487 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Tue, 27 Nov 2018 13:48:27 +0800 Subject: [PATCH 07/20] Add implementation for default token server module Signed-off-by: Eric Zhao --- .../cluster/flow/ClusterFlowChecker.java | 193 ++++++++++++++++++ .../cluster/flow/ClusterFlowRuleManager.java | 137 +++++++++++++ .../cluster/flow/ClusterParamFlowChecker.java | 82 ++++++++ .../flow/ClusterParamFlowRuleManager.java | 138 +++++++++++++ .../cluster/flow/DefaultTokenService.java | 77 +++++++ .../statistic/ClusterMetricStatistics.java | 59 ++++++ .../ClusterParamMetricStatistics.java | 59 ++++++ .../flow/statistic/data/ClusterFlowEvent.java | 42 ++++ .../statistic/data/ClusterMetricBucket.java | 49 +++++ .../flow/statistic/metric/ClusterMetric.java | 76 +++++++ .../metric/ClusterMetricLeapArray.java | 87 ++++++++ .../statistic/metric/ClusterParamMetric.java | 74 +++++++ .../metric/ClusterParameterLeapArray.java | 56 +++++ .../cluster/server/ClusterTokenServer.java | 29 +++ .../cluster/server/NettyTransportServer.java | 175 ++++++++++++++++ .../cluster/server/ServerConstants.java | 31 +++ .../cluster/server/TokenServiceProvider.java | 64 ++++++ .../codec/DefaultRequestEntityDecoder.java | 65 ++++++ .../codec/DefaultResponseEntityWriter.java | 53 +++++ .../codec/ServerEntityCodecProvider.java | 66 ++++++ .../codec/data/FlowRequestDataDecoder.java | 50 +++++ .../codec/data/FlowResponseDataWriter.java | 34 +++ .../data/ParamFlowRequestDataDecoder.java | 91 +++++++++ .../codec/netty/NettyRequestDecoder.java | 51 +++++ .../codec/netty/NettyResponseEncoder.java | 55 +++++ .../registry/RequestDataDecodeRegistry.java | 48 +++++ .../registry/ResponseDataWriterRegistry.java | 48 +++++ .../config/ClusterServerConfigManager.java | 36 ++++ .../cluster/server/connection/Connection.java | 37 ++++ .../server/connection/ConnectionGroup.java | 85 ++++++++ .../server/connection/ConnectionManager.java | 32 +++ .../server/connection/ConnectionPool.java | 149 ++++++++++++++ .../server/connection/NettyConnection.java | 85 ++++++++ .../connection/ScanIdleConnectionTask.java | 43 ++++ .../server/handler/TokenServerHandler.java | 84 ++++++++ .../processor/FlowRequestProcessor.java | 51 +++++ .../processor/ParamFlowRequestProcessor.java | 53 +++++ .../server/processor/RequestProcessor.java | 38 ++++ .../processor/RequestProcessorRegistry.java | 49 +++++ .../cluster/server/util/ClusterRuleUtil.java | 28 +++ ....alibaba.csp.sentinel.cluster.TokenService | 1 + ...cluster.codec.request.RequestEntityDecoder | 1 + ...luster.codec.response.ResponseEntityWriter | 1 + 43 files changed, 2762 insertions(+) create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java new file mode 100644 index 00000000..836d338f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java new file mode 100644 index 00000000..2f45ec63 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java @@ -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 FLOW_RULES = new ConcurrentHashMap<>(); + + private static final PropertyListener> PROPERTY_LISTENER = new FlowRulePropertyListener(); + private static SentinelProperty> 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> 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 buildClusterFlowRuleMap(List list) { + Map 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 previousSet = FLOW_RULES.keySet(); + for (Long id : previousSet) { + if (!ruleMap.containsKey(id)) { + ClusterMetricStatistics.removeMetric(id); + } + } + + return ruleMap; + } + + private static final class FlowRulePropertyListener implements PropertyListener> { + + @Override + public void configUpdate(List conf) { + Map 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 conf) { + Map rules = buildClusterFlowRuleMap(conf); + if (rules != null) { + FLOW_RULES.clear(); + FLOW_RULES.putAll(rules); + } + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded: " + FLOW_RULES); + } + } + + private ClusterFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java new file mode 100644 index 00000000..04d09668 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java @@ -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 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 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java new file mode 100644 index 00000000..c0a4278f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java @@ -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 PARAM_RULES = new ConcurrentHashMap<>(); + + private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener(); + private static SentinelProperty> currentProperty + = new DynamicSentinelProperty>(); + + 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> 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> { + + @Override + public void configUpdate(List conf) { + Map 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 conf) { + Map 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 buildClusterRuleMap(List list) { + Map 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 previousSet = PARAM_RULES.keySet(); + for (Long id : previousSet) { + if (!ruleMap.containsKey(id)) { + ClusterParamMetricStatistics.removeMetric(id); + } + } + + return ruleMap; + } + + private ClusterParamFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java new file mode 100644 index 00000000..f12a6d64 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java @@ -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 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); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java new file mode 100644 index 00000000..155cc32d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java @@ -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 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java new file mode 100644 index 00000000..73445cda --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java @@ -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 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java new file mode 100644 index 00000000..3f674ca9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java @@ -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 +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java new file mode 100644 index 00000000..6f1eee13 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java @@ -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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java new file mode 100644 index 00000000..ba992759 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java @@ -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 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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java new file mode 100644 index 00000000..86c02d07 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java @@ -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 { + + 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 resetWindowTo(WindowWrap 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(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java new file mode 100644 index 00000000..9dd52c45 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java @@ -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 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> buckets = metric.values(); + for (CacheMap 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 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(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java new file mode 100644 index 00000000..d11c64c6 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java @@ -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 counter type + * @since 1.4.0 + */ +public class ClusterParameterLeapArray extends LeapArray> { + + 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 newEmptyBucket() { + return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); + } + + @Override + protected WindowWrap> resetWindowTo(WindowWrap> w, + long startTime) { + w.value().clear(); + return w; + } + + public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java new file mode 100644 index 00000000..8d17612c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java @@ -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; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java new file mode 100644 index 00000000..5e3025b9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java @@ -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() { + @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() { + @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 connections = connectionPool.listAllConnection(); + for (Connection connection : connections) { + connection.close(); + } + } + + public List listAllClient() { + List clients = new ArrayList(); + List 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(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java new file mode 100644 index 00000000..6569cef8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java new file mode 100644 index 00000000..8e3c2706 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java @@ -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 LOADER = ServiceLoader.load(TokenService.class); + + static { + resolveTokenServiceSpi(); + } + + public static TokenService getService() { + return service; + } + + private static void resolveTokenServiceSpi() { + boolean hasOther = false; + List list = new ArrayList(); + 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java new file mode 100644 index 00000000..9012ae3f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java @@ -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; + +/** + *

    Default entity decoder for any {@link ClusterRequest} entity.

    + * + *

    Decode format:

    + *
    + * +--------+---------+---------+
    + * | xid(4) | type(1) | data... |
    + * +--------+---------+---------+
    + * 
    + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultRequestEntityDecoder implements RequestEntityDecoder { + + @Override + public ClusterRequest decode(ByteBuf source) { + if (source.readableBytes() >= 5) { + int xid = source.readInt(); + int type = source.readByte(); + + EntityDecoder 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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java new file mode 100644 index 00000000..e56faa09 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java @@ -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 { + + @Override + public void writeTo(ClusterResponse response, ByteBuf out) { + int type = response.getType(); + EntityWriter 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java new file mode 100644 index 00000000..ada283ab --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java new file mode 100644 index 00000000..d34a7c6a --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java @@ -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; + +/** + *

    + * Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout: + *

    + *
    + * | flow ID (4) | count (4) | priority flag (1) |
    + * 
    + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class FlowRequestDataDecoder implements EntityDecoder { + + @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; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java new file mode 100644 index 00000000..0f240eef --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java @@ -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 { + + @Override + public void writeTo(FlowTokenResponseData entity, ByteBuf out) { + out.writeInt(entity.getRemainingCount()); + out.writeInt(entity.getWaitInMs()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java new file mode 100644 index 00000000..b98458cf --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java @@ -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 { + + @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 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 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; + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java new file mode 100644 index 00000000..d5257f19 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java @@ -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 out) throws Exception { + RequestEntityDecoder 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); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java new file mode 100644 index 00000000..ef7e2bef --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java @@ -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 { + + @Override + protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception { + ResponseEntityWriter 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()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java new file mode 100644 index 00000000..df8070d6 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java @@ -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> DECODER_MAP = new HashMap<>(); + + public static boolean addDecoder(int type, EntityDecoder decoder) { + if (DECODER_MAP.containsKey(type)) { + return false; + } + DECODER_MAP.put(type, decoder); + return true; + } + + public static EntityDecoder getDecoder(int type) { + return (EntityDecoder)DECODER_MAP.get(type); + } + + public static boolean removeDecoder(int type) { + return DECODER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java new file mode 100644 index 00000000..6b878b75 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java @@ -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> WRITER_MAP = new HashMap<>(); + + public static boolean addWriter(int type, EntityWriter writer) { + if (WRITER_MAP.containsKey(type)) { + return false; + } + WRITER_MAP.put(type, (EntityWriter)writer); + return true; + } + + public static EntityWriter getWriter(int type) { + return WRITER_MAP.get(type); + } + + public static boolean remove(int type) { + return WRITER_MAP.remove(type) != null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java new file mode 100644 index 00000000..299a1275 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java new file mode 100644 index 00000000..29d6ecba --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java @@ -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(); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java new file mode 100644 index 00000000..6046a19c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java @@ -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 addressSet = new ConcurrentSkipListSet<>(); + private Set 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 getAddressSet() { + return addressSet; + } + + public Set getHostSet() { + return hostSet; + } + + public int getConnectedCount() { + return connectedCount.get(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java new file mode 100644 index 00000000..100ba684 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java @@ -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 CONN_MAP = new ConcurrentHashMap<>(); + + + + private ConnectionManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java new file mode 100644 index 00000000..4bc15dfb --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java @@ -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 CONNECTION_MAP = new ConcurrentHashMap(); + + /** + * 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 listAllConnection() { + List connections = new ArrayList(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."); + } + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java new file mode 100644 index 00000000..3463871e --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java new file mode 100644 index 00000000..22af12d5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java @@ -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 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. + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java new file mode 100644 index 00000000..0aed66c8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java @@ -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); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java new file mode 100644 index 00000000..827960ae --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java @@ -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 { + + @Override + public ClusterResponse processRequest(ClusterRequest 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 toResponse(TokenResult result, ClusterRequest request) { + return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), + new FlowTokenResponseData() + .setRemainingCount(result.getRemaining()) + .setWaitInMs(result.getWaitInMs()) + ); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java new file mode 100644 index 00000000..37bdf159 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java @@ -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 { + + @Override + public ClusterResponse processRequest(ClusterRequest request) { + TokenService tokenService = TokenServiceProvider.getService(); + + long flowId = request.getData().getFlowId(); + int count = request.getData().getCount(); + Collection args = request.getData().getParams(); + + TokenResult result = tokenService.requestParamToken(flowId, count, args); + return toResponse(result, request); + } + + private ClusterResponse toResponse(TokenResult result, ClusterRequest request) { + return new ClusterResponse<>(request.getId(), request.getType(), result.getStatus(), + new FlowTokenResponseData() + .setRemainingCount(result.getRemaining()) + .setWaitInMs(0) + ); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java new file mode 100644 index 00000000..8cce73ea --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java @@ -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 type of request body + * @param type of response body + * @author Eric Zhao + * @since 1.4.0 + */ +public interface RequestProcessor { + + /** + * Process the cluster request. + * + * @param request Sentinel cluster request + * @return the response after processed + */ + ClusterResponse processRequest(ClusterRequest request); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java new file mode 100644 index 00000000..87aa6bae --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java @@ -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 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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java new file mode 100644 index 00000000..d226b04d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java @@ -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() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService new file mode 100644 index 00000000..d246cf01 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder new file mode 100644 index 00000000..89ba536e --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.codec.DefaultRequestEntityDecoder \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter new file mode 100644 index 00000000..f8464a8f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.codec.DefaultResponseEntityWriter \ No newline at end of file From 9f2678eb6c401f1fcefdbcfeea5e5e8199e8d217 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:42:29 +0800 Subject: [PATCH 08/20] Update token client interface in sentinel-core - Add `start` and `stop` method for automatic control - Update TokenClientProvider using SpiLoader Signed-off-by: Eric Zhao --- .../{ => client}/ClusterTokenClient.java | 19 +++++++++++++- .../{ => client}/TokenClientProvider.java | 26 +++++++------------ 2 files changed, 27 insertions(+), 18 deletions(-) rename sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/{ => client}/ClusterTokenClient.java (68%) rename sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/{ => client}/TokenClientProvider.java (62%) diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java similarity index 68% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java index c0243974..743bfcda 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.cluster; +package com.alibaba.csp.sentinel.cluster.client; + +import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; +import com.alibaba.csp.sentinel.cluster.TokenService; /** * Token client interface for distributed flow control. @@ -29,4 +32,18 @@ public interface ClusterTokenClient extends TokenService { * @return current token server if connected, otherwise null */ TokenServerDescriptor currentServer(); + + /** + * Start the token client. + * + * @throws Exception some error occurs + */ + void start() throws Exception; + + /** + * Stop the token client. + * + * @throws Exception some error occurs + */ + void stop() throws Exception; } \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java similarity index 62% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java index 592046c9..c46a75ec 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.cluster; - -import java.util.ArrayList; -import java.util.List; -import java.util.ServiceLoader; +package com.alibaba.csp.sentinel.cluster.client; import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.SpiLoader; /** * Provider for a universal {@link ClusterTokenClient} instance. @@ -31,8 +28,6 @@ public final class TokenClientProvider { private static ClusterTokenClient client = null; - private static final ServiceLoader LOADER = ServiceLoader.load(ClusterTokenClient.class); - static { // Not strictly thread-safe, but it's OK since it will be resolved only once. resolveTokenClientInstance(); @@ -43,17 +38,14 @@ public final class TokenClientProvider { } private static void resolveTokenClientInstance() { - List clients = new ArrayList(); - for (ClusterTokenClient client : LOADER) { - clients.add(client); - } - - if (!clients.isEmpty()) { - // Get first. - client = clients.get(0); - RecordLog.info("[TokenClientProvider] Token client resolved: " + client.getClass().getCanonicalName()); + ClusterTokenClient resolvedClient = SpiLoader.loadFirstInstance(ClusterTokenClient.class); + if (resolvedClient == null) { + RecordLog.info( + "[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); } else { - RecordLog.warn("[TokenClientProvider] No existing token client, resolve failed"); + client = resolvedClient; + RecordLog.info( + "[TokenClientProvider] Cluster token client resolved: " + client.getClass().getCanonicalName()); } } From 9d42edcffa4bc220345f163ce0291d4b2abefd52 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:44:07 +0800 Subject: [PATCH 09/20] Add cluster token server interface to core and support embedded mode - Add a EmbeddedClusterTokenServer interface Signed-off-by: Eric Zhao --- .../cluster/server/ClusterTokenServer.java | 10 ++++ .../server/EmbeddedClusterTokenServer.java | 27 +++++++++++ .../EmbeddedClusterTokenServerProvider.java | 48 +++++++++++++++++++ 3 files changed, 85 insertions(+) rename {sentinel-cluster/sentinel-cluster-server-default => sentinel-core}/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java (80%) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java similarity index 80% rename from sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java index 8d17612c..f941230c 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java @@ -23,7 +23,17 @@ package com.alibaba.csp.sentinel.cluster.server; */ public interface ClusterTokenServer { + /** + * Start the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ void start() throws Exception; + /** + * Stop the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ void stop() throws Exception; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java new file mode 100644 index 00000000..03b29f93 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server; + +import com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Embedded token server interface that can work in embedded mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EmbeddedClusterTokenServer extends ClusterTokenServer, TokenService { +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java new file mode 100644 index 00000000..bd3da760 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java @@ -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; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.SpiLoader; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class EmbeddedClusterTokenServerProvider { + + private static EmbeddedClusterTokenServer server = null; + + static { + resolveInstance(); + } + + private static void resolveInstance() { + EmbeddedClusterTokenServer s = SpiLoader.loadFirstInstance(EmbeddedClusterTokenServer.class); + if (s == null) { + RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); + } else { + server = s; + RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: " + server.getClass().getCanonicalName()); + } + } + + public static EmbeddedClusterTokenServer getServer() { + return server; + } + + private EmbeddedClusterTokenServerProvider() {} +} From ba72d4c67aca9f3159a264b3a3528da9dd0cdc59 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:45:45 +0800 Subject: [PATCH 10/20] Rearrangement and refinement of statistic code in core Signed-off-by: Eric Zhao --- .../sentinel/slots/statistic/MetricEvent.java | 37 +++++++ .../slots/statistic/base/LeapArray.java | 98 +++++++++++++------ .../{base => data}/MetricBucket.java | 3 +- .../slots/statistic/metric/ArrayMetric.java | 2 +- .../slots/statistic/metric/Metric.java | 2 +- .../statistic/metric/MetricsLeapArray.java | 2 +- .../sentinel/base/metric/ArrayMetricTest.java | 2 +- .../base/metric/MetricsLeapArrayTest.java | 2 +- .../slots/statistic/base/LeapArrayTest.java | 69 +++++++++++++ 9 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java rename sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/{base => data}/MetricBucket.java (95%) create mode 100644 sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java new file mode 100644 index 00000000..f05dc966 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java @@ -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.slots.statistic; + +/** + * @author Eric Zhao + */ +public enum MetricEvent { + + /** + * Normal pass. + */ + PASS, + /** + * Normal block. + */ + BLOCK, + EXCEPTION, + SUCCESS, + RT, + OCCUPIED_PASS, + OCCUPIED_BLOCK, + WAITING +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java index 51aae33d..38061dc3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java @@ -25,12 +25,12 @@ import com.alibaba.csp.sentinel.util.TimeUtil; /** *

    - * Basic data structure for statistic metrics. + * Basic data structure for statistic metrics in Sentinel. *

    *

    - * Using sliding window algorithm to count data. Each bucket cover {@link #windowLengthInMs} time span, - * and the total time span is {@link #intervalInMs}, so the total bucket count is: - * {@link #sampleCount} = intervalInMs / windowLengthInMs. + * Leap array use sliding window algorithm to count data. Each bucket cover {code windowLengthInMs} time span, + * and the total time span is {@link #intervalInMs}, so the total bucket amount is: + * {@code sampleCount = intervalInMs / windowLengthInMs}. *

    * * @param type of statistic data @@ -47,7 +47,7 @@ public abstract class LeapArray { protected final AtomicReferenceArray> array; /** - * The fine-grained update lock is used only when current bucket is deprecated. + * The conditional (predicate) update lock is used only when current bucket is deprecated. */ private final ReentrantLock updateLock = new ReentrantLock(); @@ -58,9 +58,10 @@ public abstract class LeapArray { * @param intervalInSec the total time span of this {@link LeapArray} in seconds. */ public LeapArray(int windowLengthInMs, int intervalInSec) { + // TODO: change `intervalInSec` to `intervalInMs` AssertUtil.isTrue(windowLengthInMs > 0, "bucket length is invalid: " + windowLengthInMs); int intervalInMs = intervalInSec * 1000; - AssertUtil.isTrue(intervalInSec * 1000 > windowLengthInMs, + AssertUtil.isTrue(intervalInMs > windowLengthInMs, "total time span of the window should be greater than bucket length"); AssertUtil.isTrue(intervalInMs % windowLengthInMs == 0, "time span needs to be evenly divided"); @@ -90,28 +91,36 @@ public abstract class LeapArray { /** * Reset given bucket to provided start time and reset the value. * - * @param startTime the start time of the bucket + * @param startTime the start time of the bucket in milliseconds * @param windowWrap current bucket * @return new clean bucket at given start time */ protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime); + protected int calculateTimeIdx(/*@Valid*/ long timeMillis) { + long timeId = timeMillis / windowLengthInMs; + // Calculate current index so we can map the timestamp to the leap array. + return (int)(timeId % array.length()); + } + + protected long calculateWindowStart(/*@Valid*/ long timeMillis) { + return timeMillis - timeMillis % windowLengthInMs; + } + /** * Get bucket item at provided timestamp. * - * @param time a valid timestamp + * @param timeMillis a valid timestamp in milliseconds * @return current bucket item at provided timestamp if the time is valid; null if time is invalid */ - public WindowWrap currentWindow(long time) { - if (time < 0) { + public WindowWrap currentWindow(long timeMillis) { + if (timeMillis < 0) { return null; } - long timeId = time / windowLengthInMs; - // Calculate current index so we can map the timestamp to the leap array. - int idx = (int)(timeId % array.length()); + int idx = calculateTimeIdx(timeMillis); // Calculate current bucket start time. - long windowStart = time - time % windowLengthInMs; + long windowStart = calculateWindowStart(timeMillis); /* * Get bucket item at given time from the array. @@ -171,7 +180,7 @@ public abstract class LeapArray { * Note that the reset and clean-up operations are hard to be atomic, * so we need a update lock to guarantee the correctness of bucket update. * - * The update lock is fine-grained and will take effect only when + * The update lock is conditional (tiny scope) and will take effect only when * bucket is deprecated, so in most cases it won't lead to performance loss. */ if (updateLock.tryLock()) { @@ -195,23 +204,23 @@ public abstract class LeapArray { /** * Get the previous bucket item before provided timestamp. * - * @param time a valid timestamp + * @param timeMillis a valid timestamp in milliseconds * @return the previous bucket item before provided timestamp */ - public WindowWrap getPreviousWindow(long time) { - if (time < 0) { + public WindowWrap getPreviousWindow(long timeMillis) { + if (timeMillis < 0) { return null; } - long timeId = (time - windowLengthInMs) / windowLengthInMs; - int idx = (int)(timeId % array.length()); - time = time - windowLengthInMs; + int idx = calculateTimeIdx(timeMillis); + + long previousTime = timeMillis - windowLengthInMs; WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } - if (wrap.windowStart() + windowLengthInMs < (time)) { + if (wrap.windowStart() + windowLengthInMs < previousTime) { return null; } @@ -230,15 +239,14 @@ public abstract class LeapArray { /** * Get statistic value from bucket for provided timestamp. * - * @param time a valid timestamp + * @param time a valid timestamp in milliseconds * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null */ public T getWindowValue(long time) { if (time < 0) { return null; } - long timeId = time / windowLengthInMs; - int idx = (int)(timeId % array.length()); + int idx = calculateTimeIdx(time); WindowWrap old = array.get(idx); if (old == null || isWindowDeprecated(old)) { @@ -255,7 +263,7 @@ public abstract class LeapArray { * @param windowWrap a non-null bucket * @return true if the bucket is deprecated; otherwise false */ - private boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { + protected boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; } @@ -266,9 +274,10 @@ public abstract class LeapArray { * @return valid bucket list for entire sliding window. */ public List> list() { - List> result = new ArrayList>(); + int size = array.length(); + List> result = new ArrayList>(size); - for (int i = 0; i < array.length(); i++) { + for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(windowWrap)) { continue; @@ -286,9 +295,10 @@ public abstract class LeapArray { * @return aggregated value list for entire sliding window */ public List values() { - List result = new ArrayList(); + int size = array.length(); + List result = new ArrayList(size); - for (int i = 0; i < array.length(); i++) { + for (int i = 0; i < size; i++) { WindowWrap windowWrap = array.get(i); if (windowWrap == null || isWindowDeprecated(windowWrap)) { continue; @@ -298,6 +308,34 @@ public abstract class LeapArray { return result; } + /** + * Get the valid "head" bucket of the sliding window for provided timestamp. + * Package-private for test. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the "head" bucket if it exists and is valid; otherwise null + */ + WindowWrap getValidHead(long timeMillis) { + // Calculate index for expected head time. + int idx = calculateTimeIdx(timeMillis + windowLengthInMs); + + WindowWrap wrap = array.get(idx); + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + return wrap; + } + + /** + * Get the valid "head" bucket of the sliding window at current timestamp. + * + * @return the "head" bucket if it exists and is valid; otherwise null + */ + public WindowWrap getValidHead() { + return getValidHead(TimeUtil.currentTimeMillis()); + } + /** * Get sample count (total amount of buckets). * diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java similarity index 95% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java index d59ca984..0e96a28c 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/MetricBucket.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.slots.statistic.base; +package com.alibaba.csp.sentinel.slots.statistic.data; import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; /** * Represents metrics data in a period of time span. diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java index 7a141a54..e0c75418 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java @@ -20,7 +20,7 @@ import java.util.List; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.node.metric.MetricNode; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java index 1b62057a..954614a3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java @@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.slots.statistic.metric; import java.util.List; import com.alibaba.csp.sentinel.node.metric.MetricNode; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; /** * Represents a basic structure recording invocation metrics of protected resources. diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java index fcf452e1..2a1cda4b 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/metric/MetricsLeapArray.java @@ -16,7 +16,7 @@ package com.alibaba.csp.sentinel.slots.statistic.metric; import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; /** diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java index 02e33732..6df540cd 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/ArrayMetricTest.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import org.junit.Test; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java index 735838e4..8998895c 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/base/metric/MetricsLeapArrayTest.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; -import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; +import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java new file mode 100644 index 00000000..e5ccbda2 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArrayTest.java @@ -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.slots.statistic.base; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Eric Zhao + */ +public class LeapArrayTest { + + @Test + public void testGetValidHead() { + int windowLengthInMs = 100; + int intervalInSec = 1; + int sampleCount = intervalInSec * 1000 / windowLengthInMs; + LeapArray leapArray = new LeapArray(windowLengthInMs, intervalInSec) { + @Override + public AtomicInteger newEmptyBucket() { + return new AtomicInteger(0); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { + windowWrap.resetTo(startTime); + windowWrap.value().set(0); + return windowWrap; + } + }; + WindowWrap expected1 = leapArray.currentWindow(); + expected1.value().addAndGet(1); + sleep(windowLengthInMs); + WindowWrap expected2 = leapArray.currentWindow(); + expected2.value().addAndGet(2); + for (int i = 0; i < sampleCount - 2; i++) { + sleep(windowLengthInMs); + leapArray.currentWindow().value().addAndGet(i + 3); + } + + assertSame(expected1, leapArray.getValidHead()); + sleep(windowLengthInMs); + assertSame(expected2, leapArray.getValidHead()); + } + + private void sleep(int t) { + try { + Thread.sleep(t); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file From e31e7e020844fee3da41e48dfc91b4dfeb9b7fe5 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:47:20 +0800 Subject: [PATCH 11/20] Update cluster related logic in core - Remove "borrow-from-ref" mode - Improve flow checker to support both embedded server mode and client mode Signed-off-by: Eric Zhao --- ...til.java => ClusterClientStatLogUtil.java} | 10 +- .../slots/block/ClusterRuleConstant.java | 2 +- .../slots/block/flow/ClusterFlowConfig.java | 51 +-------- .../slots/block/flow/FlowRuleChecker.java | 100 ++++++++++++------ .../slots/block/flow/FlowRuleUtil.java | 4 +- .../slots/statistic/base/LeapArray.java | 8 +- .../csp/sentinel/util/function/Supplier.java | 29 +++++ 7 files changed, 109 insertions(+), 95 deletions(-) rename sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/{ClusterStatLogUtil.java => ClusterClientStatLogUtil.java} (89%) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java similarity index 89% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java index 16f914c6..03b1655c 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java @@ -15,8 +15,6 @@ */ package com.alibaba.csp.sentinel.cluster.log; -import java.io.File; - import com.alibaba.csp.sentinel.eagleeye.EagleEye; import com.alibaba.csp.sentinel.eagleeye.StatLogger; import com.alibaba.csp.sentinel.log.LogBase; @@ -26,16 +24,16 @@ import com.alibaba.csp.sentinel.log.LogBase; * @author Eric Zhao * @since 1.4.0 */ -public final class ClusterStatLogUtil { +public final class ClusterClientStatLogUtil { - private static final String FILE_NAME = "sentinel-cluster.log"; + private static final String FILE_NAME = "sentinel-cluster-client.log"; private static StatLogger statLogger; static { String path = LogBase.getLogBaseDir() + FILE_NAME; - statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record") + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-client-record") .intervalSeconds(1) .entryDelimiter('|') .keyDelimiter(',') @@ -55,5 +53,5 @@ public final class ClusterStatLogUtil { statLogger.stat(msg).count(count); } - private ClusterStatLogUtil() {} + private ClusterClientStatLogUtil() {} } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java index 78cacf34..c04fa554 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java @@ -22,7 +22,7 @@ package com.alibaba.csp.sentinel.slots.block; public final class ClusterRuleConstant { public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0; - public static final int FLOW_CLUSTER_STRATEGY_REF = 1; + public static final int FLOW_CLUSTER_STRATEGY_BORROW_REF = 1; public static final int FLOW_THRESHOLD_AVG_LOCAL = 0; public static final int FLOW_THRESHOLD_GLOBAL = 1; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java index b12a0743..7e3c53c4 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -34,17 +34,13 @@ public class ClusterFlowConfig { * Threshold type (average by local value or global value). */ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; - private boolean fallbackToLocalWhenFail; + private boolean fallbackToLocalWhenFail = true; /** - * 0: normal; 1: using reference (borrow from reference). + * 0: normal. */ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; - private Long refFlowId; - private int refSampleCount = 10; - private double refRatio = 1d; - public Long getFlowId() { return flowId; } @@ -72,33 +68,6 @@ public class ClusterFlowConfig { return this; } - public Long getRefFlowId() { - return refFlowId; - } - - public ClusterFlowConfig setRefFlowId(Long refFlowId) { - this.refFlowId = refFlowId; - return this; - } - - public int getRefSampleCount() { - return refSampleCount; - } - - public ClusterFlowConfig setRefSampleCount(int refSampleCount) { - this.refSampleCount = refSampleCount; - return this; - } - - public double getRefRatio() { - return refRatio; - } - - public ClusterFlowConfig setRefRatio(double refRatio) { - this.refRatio = refRatio; - return this; - } - public boolean isFallbackToLocalWhenFail() { return fallbackToLocalWhenFail; } @@ -118,24 +87,15 @@ public class ClusterFlowConfig { if (thresholdType != that.thresholdType) { return false; } if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } if (strategy != that.strategy) { return false; } - if (refSampleCount != that.refSampleCount) { return false; } - if (Double.compare(that.refRatio, refRatio) != 0) { return false; } - if (flowId != null ? !flowId.equals(that.flowId) : that.flowId != null) { return false; } - return refFlowId != null ? refFlowId.equals(that.refFlowId) : that.refFlowId == null; + return flowId != null ? flowId.equals(that.flowId) : that.flowId == null; } @Override public int hashCode() { - int result; - long temp; - result = flowId != null ? flowId.hashCode() : 0; + int result = flowId != null ? flowId.hashCode() : 0; result = 31 * result + thresholdType; result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); result = 31 * result + strategy; - result = 31 * result + (refFlowId != null ? refFlowId.hashCode() : 0); - result = 31 * result + refSampleCount; - temp = Double.doubleToLongBits(refRatio); - result = 31 * result + (int)(temp ^ (temp >>> 32)); return result; } @@ -146,9 +106,6 @@ public class ClusterFlowConfig { ", thresholdType=" + thresholdType + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + ", strategy=" + strategy + - ", refFlowId=" + refFlowId + - ", refSampleCount=" + refSampleCount + - ", refRatio=" + refRatio + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java index 7518d7c1..08cf5dd2 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java @@ -15,10 +15,12 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; -import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; -import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; 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.context.Context; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.DefaultNode; @@ -62,38 +64,6 @@ final class FlowRuleChecker { return rule.getRater().canPass(selectedNode, acquireCount); } - static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, - boolean prioritized) { - try { - ClusterTokenClient client = TokenClientProvider.getClient(); - if (client != null) { - TokenResult result = client.requestToken(rule.getClusterConfig().getFlowId(), acquireCount, - prioritized); - switch (result.getStatus()) { - case TokenResultStatus.OK: - return true; - case TokenResultStatus.SHOULD_WAIT: - // Wait for next tick. - Thread.sleep(result.getWaitInMs()); - return true; - case TokenResultStatus.NO_RULE_EXISTS: - case TokenResultStatus.BAD_REQUEST: - case TokenResultStatus.FAIL: - return passLocalCheck(rule, context, node, acquireCount, prioritized); - case TokenResultStatus.BLOCKED: - default: - return false; - } - } - // If client is absent, then fallback to local mode. - } catch (Throwable ex) { - RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); - } - // TODO: choose whether fallback to local or inactivate the rule. - // Downgrade to local flow control when token client or server for this rule is not available. - return passLocalCheck(rule, context, node, acquireCount, prioritized); - } - static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { String refResource = rule.getRefResource(); int strategy = rule.getStrategy(); @@ -153,5 +123,67 @@ final class FlowRuleChecker { return null; } + private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + try { + TokenService clusterService = pickClusterService(); + if (clusterService == null) { + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + long flowId = rule.getClusterConfig().getFlowId(); + TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized); + return applyTokenResult(result, rule, context, node, acquireCount, prioritized); + // If client is absent, then fallback to local mode. + } catch (Throwable ex) { + RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); + } + // Fallback to local flow control when token client or server for this rule is not available. + // If fallback is not enabled, then directly pass. + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + + private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } else { + // The rule won't be activated, just pass. + return true; + } + } + + private static TokenService pickClusterService() { + if (ClusterStateManager.isClient()) { + return TokenClientProvider.getClient(); + } + if (ClusterStateManager.isServer()) { + return EmbeddedClusterTokenServerProvider.getServer(); + } + return null; + } + + private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context, DefaultNode node, + int acquireCount, boolean prioritized) { + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.SHOULD_WAIT: + // Wait for next tick. + try { + Thread.sleep(result.getWaitInMs()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + case TokenResultStatus.NO_RULE_EXISTS: + case TokenResultStatus.BAD_REQUEST: + case TokenResultStatus.FAIL: + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + case TokenResultStatus.BLOCKED: + default: + return false; + } + } + private FlowRuleChecker() {} } \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java index bea269f1..63d8ba28 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -189,10 +189,8 @@ public final class FlowRuleUtil { switch (rule.getStrategy()) { case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL: return true; - case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF: - return validClusterRuleId(clusterConfig.getRefFlowId()); default: - return true; + return false; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java index 38061dc3..d3044f3c 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java @@ -211,16 +211,16 @@ public abstract class LeapArray { if (timeMillis < 0) { return null; } - int idx = calculateTimeIdx(timeMillis); - - long previousTime = timeMillis - windowLengthInMs; + long timeId = (timeMillis - windowLengthInMs) / windowLengthInMs; + int idx = (int)(timeId % array.length()); + timeMillis = timeMillis - windowLengthInMs; WindowWrap wrap = array.get(idx); if (wrap == null || isWindowDeprecated(wrap)) { return null; } - if (wrap.windowStart() + windowLengthInMs < previousTime) { + if (wrap.windowStart() + windowLengthInMs < (timeMillis)) { return null; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java new file mode 100644 index 00000000..02b3208c --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Supplier.java @@ -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.util.function; + +/** + * Supplier functional interface from JDK 8. + */ +public interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} From 9314fca8a6934e357c7e5fa35eec5812bd7ad0d0 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:48:04 +0800 Subject: [PATCH 12/20] Add cluster state manager to manage cluster mode Signed-off-by: Eric Zhao --- .../sentinel/cluster/ClusterStateManager.java | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java new file mode 100644 index 00000000..2800b2f6 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java @@ -0,0 +1,185 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.alibaba.csp.sentinel.init.InitExecutor; +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.util.TimeUtil; + +/** + *

    Global tate manager for Sentinel cluster. This enables switching between cluster client and server.

    + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterStateManager { + + public static final int CLUSTER_CLIENT = 0; + public static final int CLUSTER_SERVER = 1; + + private static volatile int mode = -1; + private static volatile long lastModified = -1; + + private static volatile SentinelProperty stateProperty = new DynamicSentinelProperty(); + private static final PropertyListener PROPERTY_LISTENER = new ClusterStatePropertyListener(); + + static { + InitExecutor.doInit(); + stateProperty.addListener(PROPERTY_LISTENER); + } + + public static void registerProperty(SentinelProperty property) { + synchronized (PROPERTY_LISTENER) { + RecordLog.info("[ClusterStateManager] Registering new property to cluster state manager"); + stateProperty.removeListener(PROPERTY_LISTENER); + property.addListener(PROPERTY_LISTENER); + stateProperty = property; + } + } + + public static int getMode() { + return mode; + } + + public static boolean isClient() { + return mode == CLUSTER_CLIENT; + } + + public static boolean isServer() { + return mode == CLUSTER_SERVER; + } + + /** + *

    + * Set current mode to client mode. If Sentinel currently works in server mode, + * it will be turned off. Then the cluster client will be started. + *

    + */ + public static void setToClient() { + if (mode == CLUSTER_CLIENT) { + return; + } + mode = CLUSTER_CLIENT; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + try { + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.stop(); + } + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to client"); + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)"); + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex); + } + } + + /** + *

    + * Set current mode to server mode. If Sentinel currently works in client mode, + * it will be turned off. Then the cluster server will be started. + *

    + */ + public static void setToServer() { + if (mode == CLUSTER_SERVER) { + return; + } + mode = CLUSTER_SERVER; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + try { + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.stop(); + } + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to server"); + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)"); + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex); + } + } + + /** + * The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). + * Or we need to wait for a while. + */ + private static void sleepIfNeeded() { + if (lastModified <= 0) { + return; + } + long now = TimeUtil.currentTimeMillis(); + long durationPast = now - lastModified; + long estimated = durationPast - MIN_INTERVAL; + if (estimated < 0) { + try { + Thread.sleep(-estimated); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static long getLastModified() { + return lastModified; + } + + private static class ClusterStatePropertyListener implements PropertyListener { + @Override + public void configLoad(Integer value) { + applyState(value); + } + + @Override + public void configUpdate(Integer value) { + applyState(value); + } + + private synchronized void applyState(Integer state) { + if (state == null || state < 0) { + return; + } + switch (state) { + case CLUSTER_CLIENT: + setToClient(); + break; + case CLUSTER_SERVER: + setToServer(); + break; + default: + RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state); + } + } + } + + private static final int MIN_INTERVAL = 10 * 1000; +} From 2689a3ff433bc32a6501b0d40a4957d1c53f4fcd Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:49:53 +0800 Subject: [PATCH 13/20] Add `@RequestType` annotation and common config supplier registry - Add a `@RequestType` annotation for common use (e.g. request handler, encoder or decoder) - Add a registry for universal config supplier (e.g. namespace of client) Signed-off-by: Eric Zhao --- .../sentinel-cluster-common-default/pom.xml | 5 +- .../cluster/annotation/RequestType.java | 41 +++++++++++++ .../registry/ConfigSupplierRegistry.java | 60 +++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java diff --git a/sentinel-cluster/sentinel-cluster-common-default/pom.xml b/sentinel-cluster/sentinel-cluster-common-default/pom.xml index d3f14e08..970432e6 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-common-default/pom.xml @@ -13,7 +13,10 @@ jar - + + com.alibaba.csp + sentinel-core + \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java new file mode 100644 index 00000000..74224b84 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/annotation/RequestType.java @@ -0,0 +1,41 @@ +/* + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Request type annotation for handlers, codes, etc. + * + * @author Eric Zhao + * @since 1.4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface RequestType { + + /** + * Type of the request to handle. + * + * @return type of the request + */ + int value(); +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java new file mode 100644 index 00000000..63107d52 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java @@ -0,0 +1,60 @@ +/* + * 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.registry; + +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.AppNameUtil; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.function.Supplier; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ConfigSupplierRegistry { + + /** + * The default namespace supplier provides appName as namespace. + */ + private static final Supplier DEFAULT_APP_NAME_SUPPLIER = new Supplier() { + @Override + public String get() { + return AppNameUtil.getAppName(); + } + }; + /** + * Registered namespace supplier. + */ + private static Supplier namespaceSupplier = DEFAULT_APP_NAME_SUPPLIER; + + /** + * Get the registered namespace supplier. + * + * @return the registered namespace supplier + */ + public static Supplier getNamespaceSupplier() { + return namespaceSupplier; + } + + public static void setNamespaceSupplier(Supplier namespaceSupplier) { + AssertUtil.notNull(namespaceSupplier, "namespaceSupplier cannot be null"); + ConfigSupplierRegistry.namespaceSupplier = namespaceSupplier; + RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: " + + namespaceSupplier.get()); + } + + private ConfigSupplierRegistry() {} +} From 9a69104f7956fee321e7ea1923370b4b5958965d Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:52:02 +0800 Subject: [PATCH 14/20] Refactor param flow checker to support embedded server mode - Add exception item extracting method in ParamFlowRule Signed-off-by: Eric Zhao --- .../block/flow/param/ParamFlowChecker.java | 106 +++++++++--------- .../slots/block/flow/param/ParamFlowRule.java | 10 +- 2 files changed, 61 insertions(+), 55 deletions(-) diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java index 2488643f..dfacaf28 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java @@ -22,8 +22,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; -import com.alibaba.csp.sentinel.cluster.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.log.RecordLog; @@ -62,57 +62,6 @@ final class ParamFlowChecker { return passLocalCheck(resourceWrapper, rule, count, value); } - @SuppressWarnings("unchecked") - private static Collection toCollection(Object value) { - if (value instanceof Collection) { - return (Collection)value; - } else if (value.getClass().isArray()) { - List params = new ArrayList(); - int length = Array.getLength(value); - for (int i = 0; i < length; i++) { - Object param = Array.get(value, i); - params.add(param); - } - return params; - } else { - return Collections.singletonList(value); - } - } - - private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, - Object value) { - try { - ClusterTokenClient client = TokenClientProvider.getClient(); - if (client == null) { - return true; - } - Collection params = toCollection(value); - - TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); - switch (result.getStatus()) { - case TokenResultStatus.OK: - return true; - case TokenResultStatus.BLOCKED: - return false; - default: - return fallbackToLocalOrPass(resourceWrapper, rule, count, params); - } - } catch (Throwable ex) { - RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); - return fallbackToLocalOrPass(resourceWrapper, rule, count, value); - } - } - - private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, - Object value) { - if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { - return passLocalCheck(resourceWrapper, rule, count, value); - } else { - // The rule won't be activated, just pass. - return true; - } - } - private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { try { @@ -165,5 +114,56 @@ final class ParamFlowChecker { return ParamFlowSlot.getParamMetric(resourceWrapper); } + @SuppressWarnings("unchecked") + private static Collection toCollection(Object value) { + if (value instanceof Collection) { + return (Collection)value; + } else if (value.getClass().isArray()) { + List params = new ArrayList(); + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + Object param = Array.get(value, i); + params.add(param); + } + return params; + } else { + return Collections.singletonList(value); + } + } + + private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + try { + ClusterTokenClient client = TokenClientProvider.getClient(); + if (client == null) { + return true; + } + Collection params = toCollection(value); + + TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params); + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.BLOCKED: + return false; + default: + return fallbackToLocalOrPass(resourceWrapper, rule, count, params); + } + } catch (Throwable ex) { + RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex); + return fallbackToLocalOrPass(resourceWrapper, rule, count, value); + } + } + + private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, + Object value) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(resourceWrapper, rule, count, value); + } else { + // The rule won't be activated, just pass. + return true; + } + } + private ParamFlowChecker() {} } diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java index 18f6db16..031dccef 100644 --- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java +++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java @@ -104,6 +104,13 @@ public class ParamFlowRule extends AbstractRule { return this; } + public Integer retrieveExclusiveItemCount(Object value) { + if (value == null || hotItems == null) { + return null; + } + return hotItems.get(value); + } + Map getParsedHotItems() { return hotItems; } @@ -126,8 +133,7 @@ public class ParamFlowRule extends AbstractRule { return clusterConfig; } - public ParamFlowRule setClusterConfig( - ParamFlowClusterConfig clusterConfig) { + public ParamFlowRule setClusterConfig(ParamFlowClusterConfig clusterConfig) { this.clusterConfig = clusterConfig; return this; } From 138c265a34e411a88c42750ec6845246a07a4e1a Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:53:42 +0800 Subject: [PATCH 15/20] Polish cluster client module - Initial work Signed-off-by: Eric Zhao --- .../cluster/client/ClientConstants.java | 1 + .../client/DefaultClusterTokenClient.java | 55 +++++++++++---- .../cluster/client/NettyTransportClient.java | 70 ++++++++++++++----- .../codec/DefaultRequestEntityWriter.java | 1 - .../codec/DefaultResponseEntityDecoder.java | 1 - .../codec/data/FlowRequestDataWriter.java | 2 +- .../data/ParamFlowRequestDataWriter.java | 1 + .../codec/data/PingRequestDataWriter.java | 38 ++++++++++ .../codec/data/PingResponseDataDecoder.java | 35 ++++++++++ .../codec/netty/NettyRequestEncoder.java | 1 - .../codec/netty/NettyResponseDecoder.java | 1 - .../registry/RequestDataWriterRegistry.java | 1 + .../client/handler/TokenClientHandler.java | 46 ++++++++++-- .../handler/TokenClientPromiseHolder.java | 16 +++-- .../init/DefaultClusterClientInitFunc.java | 53 ++++++++++++++ ...entinel.cluster.client.ClusterTokenClient} | 0 .../com.alibaba.csp.sentinel.init.InitFunc | 1 + 17 files changed, 274 insertions(+), 49 deletions(-) create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingRequestDataWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java rename sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/{com.alibaba.csp.sentinel.cluster.ClusterTokenClient => com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient} (100%) create mode 100755 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java index 9645feb5..41b1dd01 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java @@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.cluster.client; /** * @author Eric Zhao + * @since 1.4.0 */ public final class ClientConstants { diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java index 1d7bc646..d2bc0045 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -16,9 +16,9 @@ package com.alibaba.csp.sentinel.cluster.client; import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import com.alibaba.csp.sentinel.cluster.ClusterConstants; -import com.alibaba.csp.sentinel.cluster.ClusterTokenClient; import com.alibaba.csp.sentinel.cluster.ClusterTransportClient; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; @@ -26,7 +26,7 @@ import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; import com.alibaba.csp.sentinel.cluster.client.config.ServerChangeObserver; -import com.alibaba.csp.sentinel.cluster.log.ClusterStatLogUtil; +import com.alibaba.csp.sentinel.cluster.log.ClusterClientStatLogUtil; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; @@ -46,6 +46,8 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { private ClusterTransportClient transportClient; private TokenServerDescriptor serverDescriptor; + private final AtomicBoolean shouldStart = new AtomicBoolean(false); + public DefaultClusterTokenClient() { ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() { @Override @@ -53,14 +55,10 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { changeServer(clusterClientConfig); } }); + // TODO: check here, who should start the client? initNewConnection(); } - public DefaultClusterTokenClient(ClusterTransportClient transportClient) { - // TODO: only for test, remove this constructor. - this.transportClient = transportClient; - } - private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientConfig config) { if (descriptor == null || config == null) { return false; @@ -81,9 +79,9 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { try { this.transportClient = new NettyTransportClient(host, port); this.serverDescriptor = new TokenServerDescriptor(host, port); - transportClient.start(); + RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor); } catch (Exception ex) { - ex.printStackTrace(); + RecordLog.warn("[DefaultClusterTokenClient] Failed to initialize new token client", ex); } } @@ -93,17 +91,46 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { } try { // TODO: what if the client is pending init? - if (transportClient != null && transportClient.isReady()) { + if (transportClient != null) { transportClient.stop(); } // Replace with new, even if the new client is not ready. this.transportClient = new NettyTransportClient(config); this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort()); - transportClient.start(); + startClientIfScheduled(); RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor); } catch (Exception ex) { RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex); - ex.printStackTrace(); + } + } + + private void startClientIfScheduled() throws Exception { + if (shouldStart.get()) { + if (transportClient != null) { + transportClient.start(); + } + } + } + + private void stopClientIfStarted() throws Exception { + if (shouldStart.get()) { + if (transportClient != null) { + transportClient.stop(); + } + } + } + + @Override + public void start() throws Exception { + if (shouldStart.compareAndSet(false, true)) { + startClientIfScheduled(); + } + } + + @Override + public void stop() throws Exception { + if (shouldStart.compareAndSet(true, false)) { + stopClientIfStarted(); } } @@ -123,7 +150,7 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { try { return sendTokenRequest(request); } catch (Exception ex) { - ClusterStatLogUtil.log(ex.getMessage()); + ClusterClientStatLogUtil.log(ex.getMessage()); return new TokenResult(TokenResultStatus.FAIL); } } @@ -139,7 +166,7 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { try { return sendTokenRequest(request); } catch (Exception ex) { - ClusterStatLogUtil.log(ex.getMessage()); + ClusterClientStatLogUtil.log(ex.getMessage()); return new TokenResult(TokenResultStatus.FAIL); } } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java index 6fd21379..e70df0e0 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java @@ -16,6 +16,7 @@ package com.alibaba.csp.sentinel.cluster.client; import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages; @@ -56,6 +57,8 @@ import io.netty.util.concurrent.GenericFutureListener; */ public class NettyTransportClient implements ClusterTransportClient { + public static final int RECONNECT_DELAY_MS = 1000; + private final String host; private final int port; @@ -63,8 +66,9 @@ public class NettyTransportClient implements ClusterTransportClient { private NioEventLoopGroup eventLoopGroup; private TokenClientHandler clientHandler; - private AtomicInteger idGenerator = new AtomicInteger(0); - private AtomicInteger failConnectedTime = new AtomicInteger(0); + private final AtomicInteger idGenerator = new AtomicInteger(0); + private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF); + private final AtomicInteger failConnectedTime = new AtomicInteger(0); public NettyTransportClient(ClusterClientConfig clientConfig) { AssertUtil.notNull(clientConfig, "client config cannot be null"); @@ -91,7 +95,7 @@ public class NettyTransportClient implements ClusterTransportClient { .handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { - clientHandler = new TokenClientHandler(); + clientHandler = new TokenClientHandler(currentState, disconnectCallback); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2)); pipeline.addLast(new NettyResponseDecoder()); @@ -105,24 +109,44 @@ public class NettyTransportClient implements ClusterTransportClient { } private void connect(Bootstrap b) { - b.connect(host, port).addListener(new GenericFutureListener() { - @Override - public void operationComplete(ChannelFuture future) { - if (future.cause() != null) { - RecordLog.warn( - "[NettyTransportClient] Could not connect after " + failConnectedTime.get() + " times", - future.cause()); - failConnectedTime.incrementAndGet(); - channel = null; - } else { - failConnectedTime.set(0); - channel = future.channel(); - RecordLog.info("[NettyTransportClient] Successfully connect to server " + host + ":" + port); + if (currentState.compareAndSet(ClientConstants.CLIENT_STATUS_OFF, ClientConstants.CLIENT_STATUS_PENDING)) { + b.connect(host, port).addListener(new GenericFutureListener() { + @Override + public void operationComplete(ChannelFuture future) { + if (future.cause() != null) { + RecordLog.warn(String.format("[NettyTransportClient] Could not connect to <%s:%d> after %d times", + host, port, failConnectedTime.get()), future.cause()); + failConnectedTime.incrementAndGet(); + channel = null; + } else { + failConnectedTime.set(0); + channel = future.channel(); + RecordLog.info("[NettyTransportClient] Successfully connect to server <" + host + ":" + port + ">"); + } } - } - }); + }); + } } + private Runnable disconnectCallback = new Runnable() { + @Override + public void run() { + if (channel != null) { + channel.eventLoop().schedule(new Runnable() { + @Override + public void run() { + RecordLog.info("[NettyTransportClient] Reconnecting to server <" + host + ":" + port + ">"); + try { + start(); + } catch (Exception e) { + RecordLog.warn("[NettyTransportClient] Failed to reconnect to server", e); + } + } + }, RECONNECT_DELAY_MS * (failConnectedTime.get() + 1), TimeUnit.MILLISECONDS); + } + } + }; + @Override public void start() throws Exception { connect(initClientBootstrap()); @@ -130,6 +154,14 @@ public class NettyTransportClient implements ClusterTransportClient { @Override public void stop() throws Exception { + while (currentState.get() == ClientConstants.CLIENT_STATUS_PENDING) { + try { + Thread.sleep(500); + } catch (Exception ex) { + // Ignore. + } + } + if (channel != null) { channel.close(); channel = null; @@ -139,7 +171,7 @@ public class NettyTransportClient implements ClusterTransportClient { } failConnectedTime.set(0); - RecordLog.info("[NettyTransportClient] Token client stopped"); + RecordLog.info("[NettyTransportClient] Cluster transport client stopped"); } private boolean validRequest(Request request) { diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java index f3dad331..0ba9680f 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java @@ -36,7 +36,6 @@ public class DefaultRequestEntityWriter implements RequestEntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type); if (requestDataWriter == null) { - // TODO: may need to throw exception? RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{0}>," + " dropping the request", type); return; diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java index 4c1ad34e..6cb3333c 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java @@ -55,7 +55,6 @@ public class DefaultResponseEntityDecoder implements ResponseEntityDecoder { + + @Override + public void writeTo(String entity, ByteBuf target) { + if (StringUtil.isBlank(entity) || target == null) { + return; + } + byte[] bytes = entity.getBytes(); + target.writeInt(bytes.length); + target.writeBytes(bytes); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java new file mode 100644 index 00000000..e12395f0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/PingResponseDataDecoder.java @@ -0,0 +1,35 @@ +/* + * 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.client.codec.data; + +import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class PingResponseDataDecoder implements EntityDecoder { + + @Override + public Integer decode(ByteBuf source) { + if (source.readableBytes() >= 1) { + return (int) source.readByte(); + } + return -1; + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java index 50a0476e..f6141a3d 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java @@ -35,7 +35,6 @@ public class NettyRequestEncoder extends MessageToByteEncoder { protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception { RequestEntityWriter requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter(); if (requestEntityWriter == null) { - // TODO: may need to throw exception? RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request"); return; } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java index 75c15a72..5f314619 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java @@ -39,7 +39,6 @@ public class NettyResponseDecoder extends ByteToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { ResponseEntityDecoder responseDecoder = ClientEntityCodecProvider.getResponseEntityDecoder(); if (responseDecoder == null) { - // TODO: may need to throw exception? RecordLog.warn("[NettyResponseDecoder] Cannot resolve the global response entity decoder, " + "dropping the response"); return; diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java index 4ad9305d..9c0a61b4 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java @@ -24,6 +24,7 @@ import io.netty.buffer.ByteBuf; /** * @author Eric Zhao + * @since 1.4.0 */ public final class RequestDataWriterRegistry { diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java index 564b9bb5..3d36b4d8 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -17,7 +17,10 @@ package com.alibaba.csp.sentinel.cluster.client.handler; import java.util.concurrent.atomic.AtomicInteger; +import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.client.ClientConstants; +import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry; +import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; import com.alibaba.csp.sentinel.log.RecordLog; @@ -25,32 +28,63 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** + * Netty client handler for Sentinel token client. + * * @author Eric Zhao * @since 1.4.0 */ public class TokenClientHandler extends ChannelInboundHandlerAdapter { - private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF); + private final AtomicInteger currentState; + private final Runnable disconnectCallback; + + public TokenClientHandler(AtomicInteger currentState, Runnable disconnectCallback) { + this.currentState = currentState; + this.disconnectCallback = disconnectCallback; + } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - currentState.set(ClientConstants.CLIENT_STATUS_STARTED); + currentState.compareAndSet(ClientConstants.CLIENT_STATUS_PENDING, ClientConstants.CLIENT_STATUS_STARTED); + fireClientPing(ctx); RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress()); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - System.out.println(String.format("[%s] Client message recv: %s", System.currentTimeMillis(), msg)); + System.out.println(String.format("[%s] Client message recv: %s", System.currentTimeMillis(), msg)); // TODO: remove here if (msg instanceof ClusterResponse) { ClusterResponse response = (ClusterResponse) msg; + if (response.getType() == ClusterConstants.MSG_TYPE_PING) { + handlePingResponse(ctx, response); + return; + } + TokenClientPromiseHolder.completePromise(response.getId(), response); } } + private void fireClientPing(ChannelHandlerContext ctx) { + // Data body: namespace of the client. + ClusterRequest ping = new ClusterRequest().setId(0) + .setType(ClusterConstants.MSG_TYPE_PING) + .setData(ConfigSupplierRegistry.getNamespaceSupplier().get()); + ctx.writeAndFlush(ping); + } + + private void handlePingResponse(ChannelHandlerContext ctx, ClusterResponse response) { + if (response.getStatus() == ClusterConstants.RESPONSE_STATUS_OK) { + int count = (int) response.getData(); + RecordLog.info("[TokenClientHandler] Client ping OK (target server: {0}, connected count: {1})", + ctx.channel().remoteAddress(), count); + return; + } + RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {0})", ctx.channel().remoteAddress()); + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - // TODO: should close the connection when an exception is raised. RecordLog.warn("[TokenClientHandler] Client exception caught", cause); } @@ -61,7 +95,9 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - currentState.set(ClientConstants.CLIENT_STATUS_OFF); + RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: " + ctx.channel().remoteAddress()); + currentState.compareAndSet(ClientConstants.CLIENT_STATUS_STARTED, ClientConstants.CLIENT_STATUS_OFF); + disconnectCallback.run(); } public int getCurrentState() { diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java index 033d5900..b0cf0e6c 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java @@ -47,13 +47,17 @@ public final class TokenClientPromiseHolder { if (!PROMISE_MAP.containsKey(xid)) { return false; } - ChannelPromise promise = PROMISE_MAP.get(xid).getKey(); - if (promise.isDone() || promise.isCancelled()) { - return false; + SimpleEntry entry = PROMISE_MAP.get(xid); + if (entry != null) { + ChannelPromise promise = entry.getKey(); + if (promise.isDone() || promise.isCancelled()) { + return false; + } + entry.setValue(response); + promise.setSuccess(); + return true; } - PROMISE_MAP.get(xid).setValue(response); - promise.setSuccess(); - return true; + return false; } private TokenClientPromiseHolder() {} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java new file mode 100644 index 00000000..d5851bc0 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/init/DefaultClusterClientInitFunc.java @@ -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.client.init; + +import com.alibaba.csp.sentinel.cluster.client.ClientConstants; +import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowRequestDataWriter; +import com.alibaba.csp.sentinel.cluster.client.codec.data.FlowResponseDataDecoder; +import com.alibaba.csp.sentinel.cluster.client.codec.data.ParamFlowRequestDataWriter; +import com.alibaba.csp.sentinel.cluster.client.codec.data.PingRequestDataWriter; +import com.alibaba.csp.sentinel.cluster.client.codec.data.PingResponseDataDecoder; +import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry; +import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.init.InitOrder; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@InitOrder(0) +public class DefaultClusterClientInitFunc implements InitFunc { + + @Override + public void init() throws Exception { + initDefaultEntityWriters(); + initDefaultEntityDecoders(); + } + + private void initDefaultEntityWriters() { + RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PING, new PingRequestDataWriter()); + RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_FLOW, new FlowRequestDataWriter()); + RequestDataWriterRegistry.addWriter(ClientConstants.TYPE_PARAM_FLOW, new ParamFlowRequestDataWriter()); + } + + private void initDefaultEntityDecoders() { + ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PING, new PingResponseDataDecoder()); + ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_FLOW, new FlowResponseDataDecoder()); + ResponseDataDecodeRegistry.addDecoder(ClientConstants.TYPE_PARAM_FLOW, new FlowResponseDataDecoder()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient similarity index 100% rename from sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient rename to sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..b9b709cd --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.client.init.DefaultClusterClientInitFunc \ No newline at end of file From a731811d27bea054c13d4fcfc80cadac00c4c8ce Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:55:30 +0800 Subject: [PATCH 16/20] Polish default cluster server module for initial work Signed-off-by: Eric Zhao --- .../cluster/flow/ClusterFlowChecker.java | 115 +------ .../cluster/flow/ClusterFlowRuleManager.java | 137 -------- .../cluster/flow/ClusterParamFlowChecker.java | 52 ++- .../flow/ClusterParamFlowRuleManager.java | 138 -------- .../cluster/flow/DefaultTokenService.java | 12 +- .../flow/rule/ClusterFlowRuleManager.java | 320 ++++++++++++++++++ .../rule/ClusterParamFlowRuleManager.java | 307 +++++++++++++++++ .../flow/rule/NamespaceFlowProperty.java | 56 +++ .../statistic/ClusterMetricStatistics.java | 10 + .../ClusterParamMetricStatistics.java | 10 + .../flow/statistic/data/ClusterFlowEvent.java | 10 + .../statistic/data/ClusterMetricBucket.java | 1 + .../flow/statistic/metric/ClusterMetric.java | 32 +- .../metric/ClusterMetricLeapArray.java | 19 +- .../statistic/metric/ClusterParamMetric.java | 19 +- .../metric/ClusterParameterLeapArray.java | 16 +- .../server/DefaultEmbeddedTokenServer.java | 61 ++++ .../cluster/server/NettyTransportServer.java | 45 ++- .../server/SentinelDefaultTokenServer.java | 142 ++++++++ .../cluster/server/TokenServiceProvider.java | 1 + .../codec/DefaultRequestEntityDecoder.java | 1 - .../codec/data/FlowRequestDataDecoder.java | 1 - .../data/ParamFlowRequestDataDecoder.java | 4 +- .../codec/data/PingRequestDataDecoder.java | 40 +++ .../codec/data/PingResponseDataWriter.java | 36 ++ .../codec/netty/NettyRequestDecoder.java | 1 - .../config/ClusterServerConfigManager.java | 311 ++++++++++++++++- .../server/config/ServerFlowConfig.java | 97 ++++++ .../server/config/ServerTransportConfig.java | 64 ++++ .../config/ServerTransportConfigObserver.java | 30 ++ .../cluster/server/connection/Connection.java | 1 + .../connection/ConnectionDescriptor.java | 69 ++++ .../server/connection/ConnectionGroup.java | 35 +- .../server/connection/ConnectionManager.java | 74 ++++ .../server/connection/ConnectionPool.java | 17 +- .../server/connection/NettyConnection.java | 1 + .../connection/ScanIdleConnectionTask.java | 16 +- .../server/handler/TokenServerHandler.java | 61 +++- .../init/DefaultClusterServerInitFunc.java | 66 ++++ .../server/log/ClusterServerStatLogUtil.java | 56 +++ .../processor/FlowRequestProcessor.java | 3 + .../processor/ParamFlowRequestProcessor.java | 3 + ...try.java => RequestProcessorProvider.java} | 35 +- .../cluster/server/util/ClusterRuleUtil.java | 1 + ....cluster.server.EmbeddedClusterTokenServer | 1 + ....cluster.server.processor.RequestProcessor | 2 + .../com.alibaba.csp.sentinel.init.InitFunc | 1 + .../sentinel/cluster/ClusterFlowTestUtil.java | 54 +++ .../cluster/flow/ClusterFlowCheckerTest.java | 79 +++++ 49 files changed, 2157 insertions(+), 506 deletions(-) delete mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java delete mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingRequestDataDecoder.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java rename sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/{RequestProcessorRegistry.java => RequestProcessorProvider.java} (55%) create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor create mode 100755 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java index 836d338f..f9fb0108 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java @@ -17,111 +17,22 @@ package com.alibaba.csp.sentinel.cluster.flow; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; +import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; -import com.alibaba.csp.sentinel.util.TimeUtil; /** + * Flow checker for cluster flow rules. + * * @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(); - } +final class ClusterFlowChecker { private static double calcGlobalThreshold(FlowRule rule) { double count = rule.getCount(); @@ -130,20 +41,20 @@ public final class ClusterFlowChecker { return count; case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: default: - // TODO: get real connected count grouped. - int connectedCount = 1; + int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); return count * connectedCount; } } static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) { - ClusterMetric metric = ClusterMetricStatistics.getMetric(rule.getClusterConfig().getFlowId()); + Long id = rule.getClusterConfig().getFlowId(); + ClusterMetric metric = ClusterMetricStatistics.getMetric(id); if (metric == null) { return new TokenResult(TokenResultStatus.FAIL); } double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST); - double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.exceedCount; + double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.getExceedCount(); double nextRemaining = globalThreshold - latestQps - acquireCount; if (nextRemaining >= 0) { @@ -160,10 +71,13 @@ public final class ClusterFlowChecker { .setWaitInMs(0); } else { if (prioritized) { + // Try to occupy incoming buckets. double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING); - if (occupyAvg <= ClusterServerConfigManager.maxOccupyRatio * globalThreshold) { + if (occupyAvg <= ClusterServerConfigManager.getMaxOccupyRatio() * globalThreshold) { int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold); + // waitInMs > 0 indicates pre-occupy incoming buckets successfully. if (waitInMs > 0) { + ClusterServerStatLogUtil.log("flow|waiting|" + id); return new TokenResult(TokenResultStatus.SHOULD_WAIT) .setRemaining(0) .setWaitInMs(waitInMs); @@ -174,9 +88,12 @@ public final class ClusterFlowChecker { // Blocked. metric.add(ClusterFlowEvent.BLOCK, acquireCount); metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1); + ClusterServerStatLogUtil.log("flow|block|" + id, acquireCount); + ClusterServerStatLogUtil.log("flow|block_request|" + id, 1); if (prioritized) { // Add prioritized block. metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount); + ClusterServerStatLogUtil.log("flow|occupied_block|" + id, 1); } return blockedResult(); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java deleted file mode 100644 index 2f45ec63..00000000 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 FLOW_RULES = new ConcurrentHashMap<>(); - - private static final PropertyListener> PROPERTY_LISTENER = new FlowRulePropertyListener(); - private static SentinelProperty> 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> 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 buildClusterFlowRuleMap(List list) { - Map 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 previousSet = FLOW_RULES.keySet(); - for (Long id : previousSet) { - if (!ruleMap.containsKey(id)) { - ClusterMetricStatistics.removeMetric(id); - } - } - - return ruleMap; - } - - private static final class FlowRulePropertyListener implements PropertyListener> { - - @Override - public void configUpdate(List conf) { - Map 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 conf) { - Map rules = buildClusterFlowRuleMap(conf); - if (rules != null) { - FLOW_RULES.clear(); - FLOW_RULES.putAll(rules); - } - RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded: " + FLOW_RULES); - } - } - - private ClusterFlowRuleManager() {} -} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java index 04d09668..798ecf84 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java @@ -19,30 +19,36 @@ import java.util.Collection; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; +import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; /** + * @author jialiang.linjl * @author Eric Zhao + * @since 1.4.0 */ public final class ClusterParamFlowChecker { static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection values) { - ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(rule.getClusterConfig().getFlowId()); + Long id = rule.getClusterConfig().getFlowId(); + ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(id); if (metric == null) { // Unexpected state, return FAIL. return new TokenResult(TokenResultStatus.FAIL); } + double remaining = -1; 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) { + double latestQps = metric.getAvg(value); + double threshold = calcGlobalThreshold(rule, value); + double nextRemaining = threshold - latestQps - count; + remaining = nextRemaining; + if (nextRemaining < 0) { hasPassed = false; blockObject = value; break; @@ -53,30 +59,50 @@ public final class ClusterParamFlowChecker { for (Object value : values) { metric.addValue(value, count); } + ClusterServerStatLogUtil.log(String.format("param|pass|%d", id)); } else { - // TODO: log here? + ClusterServerStatLogUtil.log(String.format("param|block|%d|%s", id, blockObject)); + } + if (values.size() > 1) { + // Remaining field is unsupported for multi-values. + remaining = -1; } - return hasPassed ? newRawResponse(TokenResultStatus.OK): newRawResponse(TokenResultStatus.BLOCKED); + return hasPassed ? newPassResponse((int)remaining): newBlockResponse(); } - private static TokenResult newRawResponse(int status) { - return new TokenResult(status) + private static TokenResult newPassResponse(int remaining) { + return new TokenResult(TokenResultStatus.OK) + .setRemaining(remaining) + .setWaitInMs(0); + } + + private static TokenResult newBlockResponse() { + return new TokenResult(TokenResultStatus.BLOCKED) .setRemaining(0) .setWaitInMs(0); } - private static double calcGlobalThreshold(ParamFlowRule rule) { - double count = rule.getCount(); + private static double calcGlobalThreshold(ParamFlowRule rule, Object value) { + double count = getRawThreshold(rule, value); switch (rule.getClusterConfig().getThresholdType()) { case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL: return count; case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: default: - int connectedCount = 1; // TODO: get real connected count grouped. + int connectedCount = ClusterParamFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); return count * connectedCount; } } + private static double getRawThreshold(ParamFlowRule rule, Object value) { + Integer itemCount = rule.retrieveExclusiveItemCount(value); + if (itemCount == null) { + return rule.getCount(); + } else { + return itemCount; + } + } + private ClusterParamFlowChecker() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java deleted file mode 100644 index c0a4278f..00000000 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 PARAM_RULES = new ConcurrentHashMap<>(); - - private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener(); - private static SentinelProperty> currentProperty - = new DynamicSentinelProperty>(); - - 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> 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> { - - @Override - public void configUpdate(List conf) { - Map 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 conf) { - Map 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 buildClusterRuleMap(List list) { - Map 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 previousSet = PARAM_RULES.keySet(); - for (Long id : previousSet) { - if (!ruleMap.containsKey(id)) { - ClusterParamMetricStatistics.removeMetric(id); - } - } - - return ruleMap; - } - - private ClusterParamFlowRuleManager() {} -} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java index f12a6d64..2953a135 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java @@ -20,7 +20,8 @@ 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.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; @@ -42,24 +43,17 @@ public class DefaultTokenService implements TokenService { 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 params) { if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) { return badRequest(); } // The rule should be valid. - ParamFlowRule rule = ClusterParamFlowRuleManager.getParamFlowRuleById(ruleId); + ParamFlowRule rule = ClusterParamFlowRuleManager.getParamRuleById(ruleId); if (rule == null) { return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java new file mode 100644 index 00000000..27b6853b --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java @@ -0,0 +1,320 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.rule; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.cluster.server.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.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Function; +import com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * Manager for cluster flow rules. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterFlowRuleManager { + + /** + * The default cluster flow rule property supplier that creates a new dynamic property + * for a specific namespace to do rule management manually. + */ + public static final Function>> DEFAULT_PROPERTY_SUPPLIER = + new Function>>() { + @Override + public SentinelProperty> apply(String namespace) { + return new DynamicSentinelProperty<>(); + } + }; + + /** + * (flowId, clusterRule) + */ + private static final Map FLOW_RULES = new ConcurrentHashMap<>(); + /** + * (namespace, [flowId...]) + */ + private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); + /** + *

    This map (flowId, namespace) is used for getting connected count + * when checking a specific rule in {@code ruleId}:

    + * + *
    +     * ruleId -> namespace -> connection group -> connected count
    +     * 
    + */ + private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); + + /** + * (namespace, property-listener wrapper) + */ + private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); + /** + * Cluster flow rule property supplier for a specific namespace. + */ + private static volatile Function>> propertySupplier + = DEFAULT_PROPERTY_SUPPLIER; + + private static final Object UPDATE_LOCK = new Object(); + + static { + initDefaultProperty(); + } + + private static void initDefaultProperty() { + // The server should always support default namespace, + // so register a default property for default namespace. + SentinelProperty> defaultProperty = new DynamicSentinelProperty<>(); + String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; + registerPropertyInternal(defaultNamespace, defaultProperty); + } + + public static void setPropertySupplier(Function>> propertySupplier) { + ClusterFlowRuleManager.propertySupplier = propertySupplier; + } + + /** + * Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s. + * The property is the source of cluster {@link FlowRule}s for a specific namespace. + * + * @param namespace namespace to register + */ + public static void register2Property(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + if (propertySupplier == null) { + RecordLog.warn( + "[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property"); + return; + } + SentinelProperty> property = propertySupplier.apply(namespace); + if (property == null) { + RecordLog.warn( + "[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring"); + return; + } + synchronized (UPDATE_LOCK) { + RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager" + + " for namespace <{0}>", namespace); + registerPropertyInternal(namespace, property); + } + } + + /** + * Listen to the {@link SentinelProperty} for cluster {@link FlowRule}s if current property for namespace is absent. + * The property is the source of cluster {@link FlowRule}s for a specific namespace. + * + * @param namespace namespace to register + */ + public static void registerPropertyIfAbsent(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + if (!PROPERTY_MAP.containsKey(namespace)) { + synchronized (UPDATE_LOCK) { + if (!PROPERTY_MAP.containsKey(namespace)) { + register2Property(namespace); + } + } + } + } + + private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/ + SentinelProperty> property) { + NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); + if (oldProperty != null) { + oldProperty.getProperty().removeListener(oldProperty.getListener()); + } + PropertyListener> listener = new FlowRulePropertyListener(namespace); + property.addListener(listener); + PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); + Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (flowIdSet == null) { + resetNamespaceFlowIdMapFor(namespace); + } + } + + public static void removeProperty(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + synchronized (UPDATE_LOCK) { + NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); + if (property != null) { + property.getProperty().removeListener(property.getListener()); + PROPERTY_MAP.remove(namespace); + } + RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager" + + " for namespace <{0}>", namespace); + } + } + + private static void removePropertyListeners() { + for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { + property.getProperty().removeListener(property.getListener()); + } + } + + private static void restorePropertyListeners() { + for (NamespaceFlowProperty p : PROPERTY_MAP.values()) { + p.getProperty().removeListener(p.getListener()); + p.getProperty().addListener(p.getListener()); + } + } + + public static FlowRule getFlowRuleById(Long id) { + if (!ClusterRuleUtil.validId(id)) { + return null; + } + return FLOW_RULES.get(id); + } + + private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { + NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet()); + } + + private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { + Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (flowIdSet != null && !flowIdSet.isEmpty()) { + for (Long flowId : flowIdSet) { + FLOW_RULES.remove(flowId); + FLOW_NAMESPACE_MAP.remove(flowId); + } + flowIdSet.clear(); + } else { + resetNamespaceFlowIdMapFor(namespace); + } + } + + private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate predicate) { + Set oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (oldIdSet != null && !oldIdSet.isEmpty()) { + for (Long flowId : oldIdSet) { + if (predicate.test(flowId)) { + FLOW_RULES.remove(flowId); + FLOW_NAMESPACE_MAP.remove(flowId); + ClusterMetricStatistics.removeMetric(flowId); + } + } + oldIdSet.clear(); + } + } + + /** + * Get connected count for associated namespace of given {@code flowId}. + * + * @param flowId unique flow ID + * @return connected count + */ + public static int getConnectedCount(long flowId) { + if (flowId <= 0) { + return 0; + } + String namespace = FLOW_NAMESPACE_MAP.get(flowId); + if (namespace == null) { + return 0; + } + return ConnectionManager.getConnectedCount(namespace); + } + + private static void applyClusterFlowRule(List list, /*@Valid*/ String namespace) { + if (list == null || list.isEmpty()) { + clearAndResetRulesFor(namespace); + return; + } + final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); + + Set flowIdSet = new HashSet<>(); + + for (FlowRule rule : list) { + if (!rule.isClusterMode()) { + continue; + } + if (!FlowRuleUtil.isValidRule(rule)) { + RecordLog.warn( + "[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); + continue; + } + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + // Flow id should not be null after filtered. + Long flowId = rule.getClusterConfig().getFlowId(); + if (flowId == null) { + continue; + } + ruleMap.put(flowId, rule); + FLOW_NAMESPACE_MAP.put(flowId, namespace); + flowIdSet.add(flowId); + + // Prepare cluster metric from valid flow ID. + ClusterMetricStatistics.putMetricIfAbsent(flowId, + new ClusterMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + + // Cleanup unused cluster metrics. + clearAndResetRulesConditional(namespace, new Predicate() { + @Override + public boolean test(Long flowId) { + return !ruleMap.containsKey(flowId); + } + }); + + FLOW_RULES.putAll(ruleMap); + NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet); + } + + private static final class FlowRulePropertyListener implements PropertyListener> { + + private final String namespace; + + public FlowRulePropertyListener(String namespace) { + this.namespace = namespace; + } + + @Override + public synchronized void configUpdate(List conf) { + applyClusterFlowRule(conf, namespace); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{0}>: {1}", + namespace, FLOW_RULES); + } + + @Override + public synchronized void configLoad(List conf) { + applyClusterFlowRule(conf, namespace); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{0}>: {1}", + namespace, FLOW_RULES); + } + } + + private ClusterFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java new file mode 100644 index 00000000..d6b255bc --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java @@ -0,0 +1,307 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.rule; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.alibaba.csp.sentinel.property.PropertyListener; +import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Function; +import com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * Manager for cluster parameter flow rules. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterParamFlowRuleManager { + + /** + * The default cluster parameter flow rule property supplier that creates a new + * dynamic property for a specific namespace to manually do rule management. + */ + public static final Function>> DEFAULT_PROPERTY_SUPPLIER = + new Function>>() { + @Override + public SentinelProperty> apply(String namespace) { + return new DynamicSentinelProperty<>(); + } + }; + + /** + * (id, clusterParamRule) + */ + private static final Map PARAM_RULES = new ConcurrentHashMap<>(); + /** + * (namespace, [flowId...]) + */ + private static final Map> NAMESPACE_FLOW_ID_MAP = new ConcurrentHashMap<>(); + /** + * (flowId, namespace) + */ + private static final Map FLOW_NAMESPACE_MAP = new ConcurrentHashMap<>(); + + /** + * (namespace, property-listener wrapper) + */ + private static final Map> PROPERTY_MAP = new ConcurrentHashMap<>(); + /** + * Cluster parameter flow rule property supplier for a specific namespace. + */ + private static volatile Function>> propertySupplier + = DEFAULT_PROPERTY_SUPPLIER; + + private static final Object UPDATE_LOCK = new Object(); + + static { + initDefaultProperty(); + } + + private static void initDefaultProperty() { + SentinelProperty> defaultProperty = new DynamicSentinelProperty<>(); + String defaultNamespace = ServerConstants.DEFAULT_NAMESPACE; + registerPropertyInternal(defaultNamespace, defaultProperty); + } + + public static void setPropertySupplier( + Function>> propertySupplier) { + ClusterParamFlowRuleManager.propertySupplier = propertySupplier; + } + + /** + * Listen to the {@link SentinelProperty} for cluster {@link ParamFlowRule}s. + * The property is the source of cluster {@link ParamFlowRule}s for a specific namespace. + * + * @param namespace namespace to register + */ + public static void register2Property(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + if (propertySupplier == null) { + RecordLog.warn( + "[ClusterParamFlowRuleManager] Cluster param rule property supplier is absent, cannot register " + + "property"); + return; + } + SentinelProperty> property = propertySupplier.apply(namespace); + if (property == null) { + RecordLog.warn( + "[ClusterParamFlowRuleManager] Wrong created property from cluster param rule property supplier, " + + "ignoring"); + return; + } + synchronized (UPDATE_LOCK) { + RecordLog.info("[ClusterParamFlowRuleManager] Registering new property to cluster param rule manager" + + " for namespace <{0}>", namespace); + registerPropertyInternal(namespace, property); + } + } + + public static void registerPropertyIfAbsent(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + if (!PROPERTY_MAP.containsKey(namespace)) { + synchronized (UPDATE_LOCK) { + if (!PROPERTY_MAP.containsKey(namespace)) { + register2Property(namespace); + } + } + } + } + + private static void registerPropertyInternal(/*@NonNull*/ String namespace, /*@Valid*/ + SentinelProperty> property) { + NamespaceFlowProperty oldProperty = PROPERTY_MAP.get(namespace); + if (oldProperty != null) { + oldProperty.getProperty().removeListener(oldProperty.getListener()); + } + PropertyListener> listener = new ParamRulePropertyListener(namespace); + property.addListener(listener); + PROPERTY_MAP.put(namespace, new NamespaceFlowProperty<>(namespace, property, listener)); + Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (flowIdSet == null) { + resetNamespaceFlowIdMapFor(namespace); + } + } + + public static void removeProperty(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + synchronized (UPDATE_LOCK) { + NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); + if (property != null) { + property.getProperty().removeListener(property.getListener()); + PROPERTY_MAP.remove(namespace); + } + RecordLog.info("[ClusterParamFlowRuleManager] Removing property from cluster flow rule manager" + + " for namespace <{0}>", namespace); + } + } + + private static void removePropertyListeners() { + for (NamespaceFlowProperty property : PROPERTY_MAP.values()) { + property.getProperty().removeListener(property.getListener()); + } + } + + private static void restorePropertyListeners() { + for (NamespaceFlowProperty p : PROPERTY_MAP.values()) { + p.getProperty().removeListener(p.getListener()); + p.getProperty().addListener(p.getListener()); + } + } + + private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { + NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet()); + } + + private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { + Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (flowIdSet != null && !flowIdSet.isEmpty()) { + for (Long flowId : flowIdSet) { + PARAM_RULES.remove(flowId); + FLOW_NAMESPACE_MAP.remove(flowId); + } + flowIdSet.clear(); + } else { + resetNamespaceFlowIdMapFor(namespace); + } + } + + private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, Predicate predicate) { + Set oldIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); + if (oldIdSet != null && !oldIdSet.isEmpty()) { + for (Long flowId : oldIdSet) { + if (predicate.test(flowId)) { + PARAM_RULES.remove(flowId); + FLOW_NAMESPACE_MAP.remove(flowId); + ClusterParamMetricStatistics.removeMetric(flowId); + } + } + oldIdSet.clear(); + } + } + + public static ParamFlowRule getParamRuleById(Long id) { + if (!ClusterRuleUtil.validId(id)) { + return null; + } + return PARAM_RULES.get(id); + } + + public static int getConnectedCount(long flowId) { + if (flowId <= 0) { + return 0; + } + String namespace = FLOW_NAMESPACE_MAP.get(flowId); + if (namespace == null) { + return 0; + } + return ConnectionManager.getConnectedCount(namespace); + } + + private static class ParamRulePropertyListener implements PropertyListener> { + + private final String namespace; + + public ParamRulePropertyListener(String namespace) { + this.namespace = namespace; + } + + @Override + public void configLoad(List conf) { + applyClusterParamRules(conf, namespace); + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{0}>: {1}", + namespace, PARAM_RULES); + } + + @Override + public void configUpdate(List conf) { + applyClusterParamRules(conf, namespace); + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{0}>: {1}", + namespace, PARAM_RULES); + } + } + + private static void applyClusterParamRules(List list, /*@Valid*/ String namespace) { + if (list == null || list.isEmpty()) { + clearAndResetRulesFor(namespace); + return; + } + final ConcurrentHashMap ruleMap = new ConcurrentHashMap<>(); + + Set flowIdSet = new HashSet<>(); + + for (ParamFlowRule rule : list) { + if (!rule.isClusterMode()) { + continue; + } + if (!ParamFlowRuleUtil.isValidRule(rule)) { + RecordLog.warn( + "[ClusterParamFlowRuleManager] Ignoring invalid param flow rule when loading new flow rules: " + + rule); + continue; + } + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + ParamFlowRuleUtil.fillExceptionFlowItems(rule); + + // Flow id should not be null after filtered. + Long flowId = rule.getClusterConfig().getFlowId(); + if (flowId == null) { + continue; + } + ruleMap.put(flowId, rule); + FLOW_NAMESPACE_MAP.put(flowId, namespace); + flowIdSet.add(flowId); + + // Prepare cluster parameter metric from valid rule ID. + ClusterParamMetricStatistics.putMetricIfAbsent(flowId, + new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + + // Cleanup unused cluster parameter metrics. + clearAndResetRulesConditional(namespace, new Predicate() { + @Override + public boolean test(Long flowId) { + return !ruleMap.containsKey(flowId); + } + }); + + PARAM_RULES.putAll(ruleMap); + NAMESPACE_FLOW_ID_MAP.put(namespace, flowIdSet); + } + + private ClusterParamFlowRuleManager() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java new file mode 100644 index 00000000..37732b43 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/NamespaceFlowProperty.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.rule; + +import java.util.List; + +import com.alibaba.csp.sentinel.property.PropertyListener; +import com.alibaba.csp.sentinel.property.SentinelProperty; + +/** + * A property wrapper for list of rules of a given namespace. + * This is useful for auto-management of the property and listener. + * + * @param type of the rule + * @author Eric Zhao + * @since 1.4.0 + */ +class NamespaceFlowProperty { + + private final String namespace; + private final SentinelProperty> property; + private final PropertyListener> listener; + + public NamespaceFlowProperty(String namespace, + SentinelProperty> property, + PropertyListener> listener) { + this.namespace = namespace; + this.property = property; + this.listener = listener; + } + + public SentinelProperty> getProperty() { + return property; + } + + public String getNamespace() { + return namespace; + } + + public PropertyListener> getListener() { + return listener; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java index 155cc32d..e574f7dc 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java @@ -16,9 +16,11 @@ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** @@ -55,5 +57,13 @@ public final class ClusterMetricStatistics { return METRIC_MAP.get(id); } + public static void resetFlowMetrics() { + Set keySet = METRIC_MAP.keySet(); + for (Long id : keySet) { + METRIC_MAP.put(id, new ClusterMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + } + private ClusterMetricStatistics() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java index 73445cda..73632493 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java @@ -16,9 +16,11 @@ package com.alibaba.csp.sentinel.cluster.flow.statistic; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; import com.alibaba.csp.sentinel.util.AssertUtil; /** @@ -55,5 +57,13 @@ public final class ClusterParamMetricStatistics { return METRIC_MAP.get(id); } + public static void resetFlowMetrics() { + Set keySet = METRIC_MAP.keySet(); + for (Long id : keySet) { + METRIC_MAP.put(id, new ClusterParamMetric(ClusterServerConfigManager.getSampleCount(), + ClusterServerConfigManager.getIntervalMs())); + } + } + private ClusterParamMetricStatistics() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java index 3f674ca9..8b79094f 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java @@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.cluster.flow.statistic.data; /** * @author Eric Zhao + * @since 1.4.0 */ public enum ClusterFlowEvent { @@ -36,7 +37,16 @@ public enum ClusterFlowEvent { * Token request (from client) blocked. */ BLOCK_REQUEST, + /** + * Pass (pre-occupy incoming buckets). + */ OCCUPIED_PASS, + /** + * Block (pre-occupy incoming buckets failed). + */ OCCUPIED_BLOCK, + /** + * Waiting due to flow shaping or for next bucket tick. + */ WAITING } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java index 6f1eee13..a27dac5a 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java @@ -19,6 +19,7 @@ import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; /** * @author Eric Zhao + * @since 1.4.0 */ public class ClusterMetricBucket { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java index ba992759..eea3c459 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java @@ -19,6 +19,7 @@ import java.util.List; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket; +import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao @@ -28,8 +29,12 @@ public class ClusterMetric { private final ClusterMetricLeapArray metric; - public ClusterMetric(int windowLengthInMs, int intervalInSec) { - this.metric = new ClusterMetricLeapArray(windowLengthInMs, intervalInSec); + public ClusterMetric(int sampleCount, int intervalInMs) { + AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive"); + AssertUtil.isTrue(intervalInMs > 0, "interval should be positive"); + AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); + int windowLengthInMs = intervalInMs / sampleCount; + this.metric = new ClusterMetricLeapArray(windowLengthInMs, intervalInMs); } public void add(ClusterFlowEvent event, long count) { @@ -40,6 +45,12 @@ public class ClusterMetric { return metric.currentWindow().value().get(event); } + /** + * Get total sum for provided event in {@code intervalInSec}. + * + * @param event event to calculate + * @return total sum for event + */ public long getSum(ClusterFlowEvent event) { metric.currentWindow(); long sum = 0; @@ -51,11 +62,18 @@ public class ClusterMetric { return sum; } + /** + * Get average count for provided event per second. + * + * @param event event to calculate + * @return average count per second for event + */ public double getAvg(ClusterFlowEvent event) { return getSum(event) / metric.getIntervalInSecond(); } /** + * Try to pre-occupy upcoming buckets. * * @return time to wait for next bucket (in ms); 0 if cannot occupy next buckets */ @@ -70,7 +88,13 @@ public class ClusterMetric { } private boolean canOccupy(ClusterFlowEvent event, int acquireCount, double latestQps, double threshold) { - // TODO - return metric.getOccupiedCount(event) + latestQps + acquireCount /*- xxx*/ <= threshold; + long headPass = metric.getFirstCountOfWindow(event); + long occupiedCount = metric.getOccupiedCount(event); + // bucket to occupy (= incoming bucket) + // ↓ + // | head bucket | | | | current bucket | + // +-------------+----+----+----+----------- ----+ + // (headPass) + return latestQps + (acquireCount + occupiedCount) - headPass <= threshold; } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java index 86c02d07..40d2d752 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java @@ -31,13 +31,13 @@ public class ClusterMetricLeapArray extends LeapArray { private boolean hasOccupied = false; /** - * The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs. + * The total bucket count is: {@link #sampleCount} = intervalInMs / windowLengthInMs. * * @param windowLengthInMs a single window bucket's time length in milliseconds. - * @param intervalInSec the total time span of this {@link LeapArray} in seconds. + * @param intervalInMs the total time span of this {@link LeapArray} in milliseconds. */ - public ClusterMetricLeapArray(int windowLengthInMs, int intervalInSec) { - super(windowLengthInMs, intervalInSec); + public ClusterMetricLeapArray(int windowLengthInMs, int intervalInMs) { + super(windowLengthInMs, intervalInMs / 1000); ClusterFlowEvent[] events = ClusterFlowEvent.values(); this.occupyCounter = new LongAdder[events.length]; for (ClusterFlowEvent event : events) { @@ -84,4 +84,15 @@ public class ClusterMetricLeapArray extends LeapArray { public long getOccupiedCount(ClusterFlowEvent event) { return occupyCounter[event.ordinal()].sum(); } + + public long getFirstCountOfWindow(ClusterFlowEvent event) { + if (event == null) { + return 0; + } + WindowWrap windowWrap = getValidHead(); + if (windowWrap == null) { + return 0; + } + return windowWrap.value().get(event); + } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java index 9dd52c45..4daf7e41 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java @@ -19,20 +19,28 @@ import java.util.List; import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; +import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao + * @since 1.4.0 */ public class ClusterParamMetric { + public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000; + private final ClusterParameterLeapArray metric; - public ClusterParamMetric(int windowLengthInMs, int intervalInSec) { - this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec); + public ClusterParamMetric(int sampleCount, int intervalInMs) { + this(sampleCount, intervalInMs, DEFAULT_CLUSTER_MAX_CAPACITY); } - public ClusterParamMetric(int windowLengthInMs, int intervalInSec, int maxCapacity) { - this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec, maxCapacity); + public ClusterParamMetric(int sampleCount, int intervalInMs, int maxCapacity) { + AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive"); + AssertUtil.isTrue(intervalInMs > 0, "interval should be positive"); + AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); + int windowLengthInMs = intervalInMs / sampleCount; + this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInMs, maxCapacity); } public long getSum(Object value) { @@ -45,7 +53,8 @@ public class ClusterParamMetric { List> buckets = metric.values(); for (CacheMap bucket : buckets) { - sum += getCount(bucket.get(value)); + long count = getCount(bucket.get(value)); + sum += count; } return sum; } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java index d11c64c6..dbb603b4 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java @@ -22,20 +22,16 @@ import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWra import com.alibaba.csp.sentinel.util.AssertUtil; /** - * @author Eric Zhao * @param counter type + * @author Eric Zhao * @since 1.4.0 */ public class ClusterParameterLeapArray extends LeapArray> { 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); + public ClusterParameterLeapArray(int windowLengthInMs, int intervalInMs, int maxCapacity) { + super(windowLengthInMs, intervalInMs / 1000); AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive"); this.maxCapacity = maxCapacity; } @@ -46,11 +42,11 @@ public class ClusterParameterLeapArray extends LeapArray> } @Override - protected WindowWrap> resetWindowTo(WindowWrap> w, - long startTime) { + protected WindowWrap> resetWindowTo(WindowWrap> w, long startTime) { + w.resetTo(startTime); w.value().clear(); return w; } - public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000; + } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java new file mode 100644 index 00000000..fd95cbf2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server; + +import java.util.Collection; + +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Default embedded token server in Sentinel which wraps the {@link SentinelDefaultTokenServer} + * and the {@link TokenService} from SPI provider. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultEmbeddedTokenServer implements EmbeddedClusterTokenServer { + + private final TokenService tokenService = TokenServiceProvider.getService(); + private final ClusterTokenServer server = new SentinelDefaultTokenServer(true); + + @Override + public void start() throws Exception { + server.start(); + } + + @Override + public void stop() throws Exception { + server.stop(); + } + + @Override + public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) { + if (tokenService != null) { + return tokenService.requestToken(ruleId, acquireCount, prioritized); + } + return new TokenResult(TokenResultStatus.FAIL); + } + + @Override + public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params) { + if (tokenService != null) { + return tokenService.requestParamToken(ruleId, acquireCount, params); + } + return new TokenResult(TokenResultStatus.FAIL); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java index 5e3025b9..aded579a 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java @@ -46,13 +46,16 @@ import static com.alibaba.csp.sentinel.cluster.server.ServerConstants.*; /** * @author Eric Zhao + * @since 1.4.0 */ public class NettyTransportServer implements ClusterTokenServer { - private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( - "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); + private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, + SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); + private static final int MAX_RETRY_TIMES = 3; + private static final int RETRY_SLEEP_MS = 1000; - private final int port = 11111; + private final int port; private NioEventLoopGroup bossGroup; private NioEventLoopGroup workerGroup; @@ -62,6 +65,10 @@ public class NettyTransportServer implements ClusterTokenServer { private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF); private final AtomicInteger failedTimes = new AtomicInteger(0); + public NettyTransportServer(int port) { + this.port = port; + } + @Override public void start() { if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) { @@ -92,23 +99,27 @@ public class NettyTransportServer implements ClusterTokenServer { .childOption(ChannelOption.SO_TIMEOUT, 10) .childOption(ChannelOption.TCP_NODELAY, true) .childOption(ChannelOption.SO_RCVBUF, 32 * 1024); - b.bind(Integer.valueOf(port)).addListener(new GenericFutureListener() { + b.bind(port).addListener(new GenericFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.cause() != null) { - RecordLog.info("Token server start failed", future.cause()); + RecordLog.info("[NettyTransportServer] Token server start failed (port=" + port + ")", + future.cause()); currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF); + int failCount = failedTimes.incrementAndGet(); + if (failCount > MAX_RETRY_TIMES) { + return; + } - //try { - // Thread.sleep((failStartTimes.get() + 1) * 1000); - // start(); - //} catch (Throwable e) { - // RecordLog.info("Fail to start token server:", e); - //} + try { + Thread.sleep(failCount * RETRY_SLEEP_MS); + start(); + } catch (Throwable e) { + RecordLog.info("[NettyTransportServer] Failed to start token server when retrying", e); + } } else { - RecordLog.info("Token server start success"); + RecordLog.info("[NettyTransportServer] Token server started success at port " + port); currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED); - //failStartTimes.set(0); } } }); @@ -119,9 +130,9 @@ public class NettyTransportServer implements ClusterTokenServer { // If still initializing, wait for ready. while (currentState.get() == SERVER_STATUS_STARTING) { try { - Thread.sleep(1000); + Thread.sleep(500); } catch (InterruptedException e) { - e.printStackTrace(); + // Ignore. } } @@ -133,9 +144,9 @@ public class NettyTransportServer implements ClusterTokenServer { failedTimes.set(0); - RecordLog.info("Token server stopped"); + RecordLog.info("[NettyTransportServer] Sentinel token server stopped"); } catch (Exception ex) { - RecordLog.warn("Failed to stop token server", ex); + RecordLog.warn("[NettyTransportServer] Failed to stop token server (port=" + port + ")", ex); } } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java new file mode 100644 index 00000000..c6a42db9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java @@ -0,0 +1,142 @@ +/* + * 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.concurrent.atomic.AtomicBoolean; + +import com.alibaba.csp.sentinel.cluster.registry.ConfigSupplierRegistry; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfigObserver; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.HostNameUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class SentinelDefaultTokenServer implements ClusterTokenServer { + + private final boolean embedded; + + private ClusterTokenServer server; + private int port; + private final AtomicBoolean shouldStart = new AtomicBoolean(false); + + static { + InitExecutor.doInit(); + } + + public SentinelDefaultTokenServer() { + this(false); + } + + public SentinelDefaultTokenServer(boolean embedded) { + this.embedded = embedded; + ClusterServerConfigManager.addTransportConfigChangeObserver(new ServerTransportConfigObserver() { + @Override + public void onTransportConfigChange(ServerTransportConfig config) { + changeServerConfig(config); + } + }); + initNewServer(); + } + + private void initNewServer() { + if (server != null) { + return; + } + int port = ClusterServerConfigManager.getPort(); + if (port > 0) { + this.server = new NettyTransportServer(port); + this.port = port; + } + } + + private synchronized void changeServerConfig(ServerTransportConfig config) { + if (config == null || config.getPort() <= 0) { + return; + } + int newPort = config.getPort(); + if (newPort == port) { + return; + } + try { + if (server != null) { + stopServerIfStarted(); + } + this.server = new NettyTransportServer(newPort); + this.port = newPort; + startServerIfScheduled(); + } catch (Exception ex) { + RecordLog.warn("[SentinelDefaultTokenServer] Failed to apply modification to token server", ex); + } + } + + private void startServerIfScheduled() throws Exception { + if (shouldStart.get()) { + if (server != null) { + server.start(); + if (embedded) { + RecordLog.info("[SentinelDefaultTokenServer] Running in embedded mode"); + handleEmbeddedStart(); + } + } + } + } + + private void stopServerIfStarted() throws Exception { + if (shouldStart.get()) { + if (server != null) { + server.stop(); + if (embedded) { + handleEmbeddedStop(); + } + } + } + } + + private void handleEmbeddedStop() { + String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get(); + if (StringUtil.isNotEmpty(namespace)) { + ConnectionManager.removeConnection(namespace, HostNameUtil.getIp()); + } + } + + private void handleEmbeddedStart() { + String namespace = ConfigSupplierRegistry.getNamespaceSupplier().get(); + if (StringUtil.isNotEmpty(namespace)) { + ConnectionManager.addConnection(namespace, HostNameUtil.getIp()); + } + } + + @Override + public void start() throws Exception { + if (shouldStart.compareAndSet(false, true)) { + startServerIfScheduled(); + } + } + + @Override + public void stop() throws Exception { + if (shouldStart.compareAndSet(true, false)) { + stopServerIfStarted(); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java index 8e3c2706..969e5400 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java @@ -52,6 +52,7 @@ public final class TokenServiceProvider { } if (hasOther) { + // Pick the first. service = list.get(0); } else { // No custom token service, using default. diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java index 9012ae3f..bdd42e64 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java @@ -54,7 +54,6 @@ public class DefaultRequestEntityDecoder implements RequestEntityDecoder { @@ -38,7 +40,6 @@ public class ParamFlowRequestDataDecoder implements EntityDecoder 0) { - // TODO: should check rules exist here? List params = new ArrayList<>(amount); for (int i = 0; i < amount; i++) { decodeParam(source, params); @@ -48,7 +49,6 @@ public class ParamFlowRequestDataDecoder implements EntityDecoder { + + @Override + public String decode(ByteBuf source) { + if (source.readableBytes() >= 4) { + int length = source.readInt(); + if (length > 0 && source.readableBytes() > 0) { + byte[] bytes = new byte[length]; + source.readBytes(bytes); + return new String(bytes); + } + } + return null; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java new file mode 100644 index 00000000..26e31ecd --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/PingResponseDataWriter.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.codec.data; + +import com.alibaba.csp.sentinel.cluster.codec.EntityWriter; +import com.alibaba.csp.sentinel.util.StringUtil; + +import io.netty.buffer.ByteBuf; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class PingResponseDataWriter implements EntityWriter { + + @Override + public void writeTo(Integer entity, ByteBuf target) { + if (entity == null || target == null) { + return; + } + target.writeByte(entity); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java index d5257f19..f5c37b34 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java @@ -36,7 +36,6 @@ public class NettyRequestDecoder extends ByteToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { RequestEntityDecoder 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; diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java index 299a1275..4054d824 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java @@ -15,22 +15,317 @@ */ package com.alibaba.csp.sentinel.cluster.server.config; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics; +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.alibaba.csp.sentinel.property.PropertyListener; +import com.alibaba.csp.sentinel.property.SentinelProperty; +import com.alibaba.csp.sentinel.util.AssertUtil; + /** * @author Eric Zhao + * @since 1.4.0 */ public final class ClusterServerConfigManager { - private static final int DEFAULT_PORT = 8730; - private static final int DEFAULT_IDLE_SECONDS = 600; + /** + * Server global transport and scope config. + */ + private static volatile int port = ServerTransportConfig.DEFAULT_PORT; + private static volatile int idleSeconds = ServerTransportConfig.DEFAULT_IDLE_SECONDS; + private static volatile Set namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); - public static volatile int port = DEFAULT_PORT; + /** + * Server global flow config. + */ + private static volatile double exceedCount = ServerFlowConfig.DEFAULT_EXCEED_COUNT; + private static volatile double maxOccupyRatio = ServerFlowConfig.DEFAULT_MAX_OCCUPY_RATIO; + private static volatile int intervalMs = ServerFlowConfig.DEFAULT_INTERVAL_MS; + private static volatile int sampleCount = ServerFlowConfig.DEFAULT_SAMPLE_COUNT; - 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; + /** + * Namespace-specific flow config for token server. + * Format: (namespace, config). + */ + private static final Map NAMESPACE_CONF = new ConcurrentHashMap<>(); - // TODO: implement here. + private static final List TRANSPORT_CONFIG_OBSERVERS = new ArrayList<>(); + + /** + * Property for cluster server global transport configuration. + */ + private static SentinelProperty transportConfigProperty = new DynamicSentinelProperty<>(); + /** + * Property for cluster server namespace set. + */ + private static SentinelProperty> namespaceSetProperty = new DynamicSentinelProperty<>(); + /** + * Property for cluster server global flow control configuration. + */ + private static SentinelProperty globalFlowProperty = new DynamicSentinelProperty<>(); + + private static final PropertyListener TRANSPORT_PROPERTY_LISTENER + = new ServerGlobalTransportPropertyListener(); + private static final PropertyListener GLOBAL_FLOW_PROPERTY_LISTENER + = new ServerGlobalFlowPropertyListener(); + private static final PropertyListener> NAMESPACE_SET_PROPERTY_LISTENER + = new ServerNamespaceSetPropertyListener(); + + static { + transportConfigProperty.addListener(TRANSPORT_PROPERTY_LISTENER); + globalFlowProperty.addListener(GLOBAL_FLOW_PROPERTY_LISTENER); + namespaceSetProperty.addListener(NAMESPACE_SET_PROPERTY_LISTENER); + } + + public static void registerNamespaceSetProperty(SentinelProperty> property) { + synchronized (NAMESPACE_SET_PROPERTY_LISTENER) { + RecordLog.info( + "[ClusterServerConfigManager] Registering new namespace set dynamic property to Sentinel server " + + "config manager"); + namespaceSetProperty.removeListener(NAMESPACE_SET_PROPERTY_LISTENER); + property.addListener(NAMESPACE_SET_PROPERTY_LISTENER); + namespaceSetProperty = property; + } + } + + public static void registerServerTransportProperty(SentinelProperty property) { + synchronized (TRANSPORT_PROPERTY_LISTENER) { + RecordLog.info( + "[ClusterServerConfigManager] Registering new server transport dynamic property to Sentinel server " + + "config manager"); + transportConfigProperty.removeListener(TRANSPORT_PROPERTY_LISTENER); + property.addListener(TRANSPORT_PROPERTY_LISTENER); + transportConfigProperty = property; + } + } + + private static class ServerNamespaceSetPropertyListener implements PropertyListener> { + + @Override + public synchronized void configLoad(Set set) { + if (set == null || set.isEmpty()) { + RecordLog.warn("[ClusterServerConfigManager] WARN: empty initial server namespace set"); + return; + } + applyNamespaceSetChange(set); + } + + @Override + public synchronized void configUpdate(Set set) { + // TODO: should debounce? + applyNamespaceSetChange(set); + } + } + + private static void applyNamespaceSetChange(Set newSet) { + if (newSet == null) { + return; + } + RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: " + newSet); + if (newSet.isEmpty()) { + ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); + return; + } + newSet.add(ServerConstants.DEFAULT_NAMESPACE); + + Set oldSet = ClusterServerConfigManager.namespaceSet; + if (oldSet != null && !oldSet.isEmpty()) { + for (String ns : oldSet) { + if (!newSet.contains(ns)) { + ClusterFlowRuleManager.removeProperty(ns); + ClusterParamFlowRuleManager.removeProperty(ns); + } + } + } + + ClusterServerConfigManager.namespaceSet = newSet; + for (String ns : newSet) { + ClusterFlowRuleManager.registerPropertyIfAbsent(ns); + ClusterParamFlowRuleManager.registerPropertyIfAbsent(ns); + } + } + + private static class ServerGlobalTransportPropertyListener implements PropertyListener { + + @Override + public void configLoad(ServerTransportConfig config) { + if (config == null) { + RecordLog.warn("[ClusterServerConfigManager] Empty initial server transport config"); + return; + } + applyConfig(config); + } + + @Override + public void configUpdate(ServerTransportConfig config) { + applyConfig(config); + } + + private synchronized void applyConfig(ServerTransportConfig config) { + if (!isValidTransportConfig(config)) { + RecordLog.warn( + "[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: " + config); + if (config.getIdleSeconds() != idleSeconds) { + idleSeconds = config.getIdleSeconds(); + } + updateTokenServer(config); + } + } + + private static class ServerGlobalFlowPropertyListener implements PropertyListener { + + @Override + public void configUpdate(ServerFlowConfig config) { + applyGlobalFlowConfig(config); + } + + @Override + public void configLoad(ServerFlowConfig 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(); + } + } + } + + 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) { + return config != null && config.getPort() > 0; + } + + public static boolean isValidFlowConfig(ServerFlowConfig config) { + 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) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getExceedCount(); + } + return exceedCount; + } + + public static double getMaxOccupyRatio(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getMaxOccupyRatio(); + } + return maxOccupyRatio; + } + + public static int getIntervalMs(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getIntervalMs(); + } + return intervalMs; + } + + /** + * Get sample count of provided namespace. + * + * @param namespace valid namespace + * @return the sample count of namespace; if the namespace does not have customized value, use the global value + */ + public static int getSampleCount(String namespace) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + ServerFlowConfig config = NAMESPACE_CONF.get(namespace); + if (config != null) { + return config.getSampleCount(); + } + return sampleCount; + } + + public static double getExceedCount() { + return exceedCount; + } + + public static double getMaxOccupyRatio() { + return maxOccupyRatio; + } + + public static Set getNamespaceSet() { + return namespaceSet; + } + + public static int getPort() { + return port; + } + + public static int getIdleSeconds() { + return idleSeconds; + } + + public static int getIntervalMs() { + return intervalMs; + } + + public static int getSampleCount() { + return sampleCount; + } + + public static void setNamespaceSet(Set namespaceSet) { + applyNamespaceSetChange(namespaceSet); + } private ClusterServerConfigManager() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java new file mode 100644 index 00000000..00991605 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerFlowConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.config; + +import com.alibaba.csp.sentinel.cluster.server.ServerConstants; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ServerFlowConfig { + + public static final double DEFAULT_EXCEED_COUNT = 1.0d; + public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d; + + public static final int DEFAULT_INTERVAL_MS = 1000; + public static final int DEFAULT_SAMPLE_COUNT= 10; + + private final String namespace; + + private double exceedCount = DEFAULT_EXCEED_COUNT; + private double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO; + private int intervalMs = DEFAULT_INTERVAL_MS; + private int sampleCount = DEFAULT_SAMPLE_COUNT; + + public ServerFlowConfig() { + this(ServerConstants.DEFAULT_NAMESPACE); + } + + public ServerFlowConfig(String namespace) { + this.namespace = namespace; + } + + public String getNamespace() { + return namespace; + } + + public double getExceedCount() { + return exceedCount; + } + + public ServerFlowConfig setExceedCount(double exceedCount) { + this.exceedCount = exceedCount; + return this; + } + + public double getMaxOccupyRatio() { + return maxOccupyRatio; + } + + public ServerFlowConfig setMaxOccupyRatio(double maxOccupyRatio) { + this.maxOccupyRatio = maxOccupyRatio; + return this; + } + + public int getIntervalMs() { + return intervalMs; + } + + public ServerFlowConfig setIntervalMs(int intervalMs) { + this.intervalMs = intervalMs; + return this; + } + + public int getSampleCount() { + return sampleCount; + } + + public ServerFlowConfig setSampleCount(int sampleCount) { + this.sampleCount = sampleCount; + return this; + } + + @Override + public String toString() { + return "ServerFlowConfig{" + + "namespace='" + namespace + '\'' + + ", exceedCount=" + exceedCount + + ", maxOccupyRatio=" + maxOccupyRatio + + ", intervalMs=" + intervalMs + + ", sampleCount=" + sampleCount + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java new file mode 100644 index 00000000..74575ede --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfig.java @@ -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.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ServerTransportConfig { + + public static final int DEFAULT_PORT = 8730; + public static final int DEFAULT_IDLE_SECONDS = 600; + + private int port; + private int idleSeconds; + + public ServerTransportConfig() { + this(DEFAULT_PORT, DEFAULT_IDLE_SECONDS); + } + + public ServerTransportConfig(int port, int idleSeconds) { + this.port = port; + this.idleSeconds = idleSeconds; + } + + public int getPort() { + return port; + } + + public ServerTransportConfig setPort(int port) { + this.port = port; + return this; + } + + public int getIdleSeconds() { + return idleSeconds; + } + + public ServerTransportConfig setIdleSeconds(int idleSeconds) { + this.idleSeconds = idleSeconds; + return this; + } + + @Override + public String toString() { + return "ServerTransportConfig{" + + "port=" + port + + ", idleSeconds=" + idleSeconds + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java new file mode 100644 index 00000000..deccf380 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ServerTransportConfigObserver.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.config; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ServerTransportConfigObserver { + + /** + * Callback on server transport config (e.g. port) change. + * + * @param config new server transport config + */ + void onTransportConfigChange(ServerTransportConfig config); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java index 29d6ecba..a409c661 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java @@ -20,6 +20,7 @@ import java.net.SocketAddress; /** * @author xuyue * @author Eric Zhao + * @since 1.4.0 */ public interface Connection extends AutoCloseable { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java new file mode 100644 index 00000000..5dd107a2 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionDescriptor.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.connection; + +import java.util.Objects; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class ConnectionDescriptor { + + private String address; + private String host; + + public String getAddress() { + return address; + } + + public ConnectionDescriptor setAddress(String address) { + this.address = address; + return this; + } + + public String getHost() { + return host; + } + + public ConnectionDescriptor setHost(String host) { + this.host = host; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + + ConnectionDescriptor that = (ConnectionDescriptor)o; + + return Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return address != null ? address.hashCode() : 0; + } + + @Override + public String toString() { + return "ConnectionDescriptor{" + + "address='" + address + '\'' + + ", host='" + host + '\'' + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java index 6046a19c..c4f84719 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java @@ -15,6 +15,8 @@ */ package com.alibaba.csp.sentinel.cluster.server.connection; +import java.util.Collections; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; @@ -23,16 +25,17 @@ import com.alibaba.csp.sentinel.cluster.server.ServerConstants; import com.alibaba.csp.sentinel.util.AssertUtil; /** + * The connection group stores connection set for a specific namespace. + * * @author Eric Zhao * @since 1.4.0 */ public class ConnectionGroup { - private String namespace; + private final String namespace; - private Set addressSet = new ConcurrentSkipListSet<>(); - private Set hostSet = new ConcurrentSkipListSet<>(); - private AtomicInteger connectedCount = new AtomicInteger(); + private final Set connectionSet = Collections.synchronizedSet(new HashSet()); + private final AtomicInteger connectedCount = new AtomicInteger(); public ConnectionGroup(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); @@ -46,24 +49,26 @@ public class ConnectionGroup { public ConnectionGroup addConnection(String address) { AssertUtil.notEmpty(address, "address cannot be empty"); - addressSet.add(address); String[] ip = address.split(":"); + String host; if (ip != null && ip.length >= 1) { - hostSet.add(ip[0]); + host = ip[0]; + } else { + host = address; } + connectionSet.add(new ConnectionDescriptor().setAddress(address).setHost(host)); 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]); + if (connectionSet.remove(new ConnectionDescriptor().setAddress(address))) { + connectedCount.decrementAndGet(); } - connectedCount.decrementAndGet(); + return this; } @@ -71,12 +76,8 @@ public class ConnectionGroup { return namespace; } - public Set getAddressSet() { - return addressSet; - } - - public Set getHostSet() { - return hostSet; + public Set getConnectionSet() { + return connectionSet; } public int getConnectedCount() { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java index 100ba684..8ce244fe 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java @@ -18,15 +18,89 @@ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.AssertUtil; + /** + * Manager for namespace-scope {@link ConnectionGroup}. + * * @author Eric Zhao * @since 1.4.0 */ public final class ConnectionManager { + /** + * Connection map (namespace, connection). + */ private static final Map CONN_MAP = new ConcurrentHashMap<>(); + /** + * namespace map (address, namespace). + */ + private static final Map NAMESPACE_MAP = new ConcurrentHashMap<>(); + /** + * Get connected count for specific namespace. + * + * @param namespace namespace to check + * @return connected count for specific namespace + */ + public static int getConnectedCount(String namespace) { + AssertUtil.notEmpty(namespace, "namespace should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + return group == null ? 0 : group.getConnectedCount(); + } + public static ConnectionGroup getOrCreateGroup(String namespace) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + synchronized (CREATE_LOCK) { + if (CONN_MAP.get(namespace) == null) { + group = new ConnectionGroup(namespace); + CONN_MAP.put(namespace, group); + } + } + } + return group; + } + + public static void removeConnection(String address) { + AssertUtil.assertNotBlank(address, "address should not be empty"); + String namespace = NAMESPACE_MAP.get(address); + if (namespace != null) { + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + return; + } + group.removeConnection(address); + RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + } + NAMESPACE_MAP.remove(address); + } + + public static void removeConnection(String namespace, String address) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + AssertUtil.assertNotBlank(address, "address should not be empty"); + ConnectionGroup group = CONN_MAP.get(namespace); + if (group == null) { + return; + } + group.removeConnection(address); + NAMESPACE_MAP.remove(address); + RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + } + + public static ConnectionGroup addConnection(String namespace, String address) { + AssertUtil.assertNotBlank(namespace, "namespace should not be empty"); + AssertUtil.assertNotBlank(address, "address should not be empty"); + ConnectionGroup group = getOrCreateGroup(namespace); + group.addConnection(address); + NAMESPACE_MAP.put(address, namespace); + RecordLog.info("[ConnectionManager] Client <{0}> registered with namespace <{1}>", address, namespace); + return group; + } + + private static final Object CREATE_LOCK = new Object(); private ConnectionManager() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java index 4bc15dfb..e1e320ce 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java @@ -50,11 +50,6 @@ public class ConnectionPool { */ private ScheduledFuture scanTaskFuture = null; - /** - * 创建一个connection,并放入连接池中 - * - * @param channel - */ public void createConnection(Channel channel) { if (channel != null) { Connection connection = new NettyConnection(channel, this); @@ -83,7 +78,7 @@ public class ConnectionPool { * @return formatted key */ private String getConnectionKey(Channel channel) { - InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); + InetSocketAddress socketAddress = (InetSocketAddress)channel.remoteAddress(); String remoteIp = socketAddress.getAddress().getHostAddress(); int remotePort = socketAddress.getPort(); return remoteIp + ":" + remotePort; @@ -93,16 +88,10 @@ public class ConnectionPool { 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()); } @@ -124,7 +113,7 @@ public class ConnectionPool { return connections; } - public int count(){ + public int count() { return CONNECTION_MAP.size(); } @@ -141,7 +130,7 @@ public class ConnectionPool { public void refreshIdleTask() { if (scanTaskFuture == null || scanTaskFuture.cancel(false)) { startScan(); - }else { + } else { RecordLog.info("The result of canceling scanTask is error."); } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java index 3463871e..a3351ced 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java @@ -22,6 +22,7 @@ import io.netty.channel.Channel; /** * @author xuyue + * @since 1.4.0 */ public class NettyConnection implements Connection { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java index 22af12d5..a559aeeb 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java @@ -3,15 +3,17 @@ package com.alibaba.csp.sentinel.cluster.server.connection; import java.util.List; import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; import com.alibaba.csp.sentinel.log.RecordLog; /** * @author xuyue * @author Eric Zhao + * @since 1.4.0 */ public class ScanIdleConnectionTask implements Runnable { - private ConnectionPool connectionPool; + private final ConnectionPool connectionPool; public ScanIdleConnectionTask(ConnectionPool connectionPool) { this.connectionPool = connectionPool; @@ -20,15 +22,15 @@ public class ScanIdleConnectionTask implements Runnable { @Override public void run() { try { - int idleSeconds = ClusterServerConfigManager.idleSeconds; - long idleTime = idleSeconds * 1000; - if (idleTime < 0) { - idleTime = 600 * 1000; + int idleSeconds = ClusterServerConfigManager.getIdleSeconds(); + long idleTimeMillis = idleSeconds * 1000; + if (idleTimeMillis < 0) { + idleTimeMillis = ServerTransportConfig.DEFAULT_IDLE_SECONDS * 1000; } long now = System.currentTimeMillis(); List connections = connectionPool.listAllConnection(); for (Connection conn : connections) { - if ((now - conn.getLastReadTime()) > idleTime) { + if ((now - conn.getLastReadTime()) > idleTimeMillis) { 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) @@ -37,7 +39,7 @@ public class ScanIdleConnectionTask implements Runnable { } } } catch (Throwable t) { - // TODO: should log here. + RecordLog.warn("[ScanIdleConnectionTask] Failed to clean-up idle tasks", t); } } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java index 0aed66c8..69d113cb 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java @@ -18,11 +18,12 @@ 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.ConnectionManager; import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool; import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor; -import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorRegistry; +import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -35,36 +36,46 @@ import io.netty.channel.ChannelInboundHandlerAdapter; */ public class TokenServerHandler extends ChannelInboundHandlerAdapter { - private final ConnectionPool connectionPool; + private final ConnectionPool globalConnectionPool; - public TokenServerHandler(ConnectionPool connectionPool) { - this.connectionPool = connectionPool; + public TokenServerHandler(ConnectionPool globalConnectionPool) { + this.globalConnectionPool = globalConnectionPool; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - System.out.println("[TokenServerHandler] Connection established"); - super.channelActive(ctx); + globalConnectionPool.createConnection(ctx.channel()); + String remoteAddress = getRemoteAddress(ctx); + System.out.println("[TokenServerHandler] Connection established, remote client address: " + remoteAddress); //TODO: DEBUG } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - System.out.println("[TokenServerHandler] Connection inactive"); - super.channelInactive(ctx); + String remoteAddress = getRemoteAddress(ctx); + System.out.println("[TokenServerHandler] Connection inactive, remote client address: " + remoteAddress); //TODO: DEBUG + globalConnectionPool.remove(ctx.channel()); + ConnectionManager.removeConnection(remoteAddress); } @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)); + globalConnectionPool.refreshLastReadTime(ctx.channel()); + System.out.println(String.format("[%s] Server message recv: %s", System.currentTimeMillis(), msg)); //TODO: DEBUG if (msg instanceof ClusterRequest) { ClusterRequest request = (ClusterRequest)msg; - RequestProcessor processor = RequestProcessorRegistry.getProcessor(request.getType()); + // Client ping with its namespace, add to connection manager. + if (request.getType() == ClusterConstants.MSG_TYPE_PING) { + handlePingRequest(ctx, request); + return; + } + + // Pick request processor for request type. + RequestProcessor processor = RequestProcessorProvider.getProcessor(request.getType()); if (processor == null) { - System.out.println("[TokenServerHandler] No processor for request type: " + request.getType()); - writeNoProcessorResponse(ctx, request); + RecordLog.warn("[TokenServerHandler] No processor for request type: " + request.getType()); + writeBadResponse(ctx, request); } else { ClusterResponse response = processor.processRequest(request); writeResponse(ctx, response); @@ -72,7 +83,7 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { } } - private void writeNoProcessorResponse(ChannelHandlerContext ctx, ClusterRequest request) { + private void writeBadResponse(ChannelHandlerContext ctx, ClusterRequest request) { ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), ClusterConstants.RESPONSE_STATUS_BAD, null); writeResponse(ctx, response); @@ -81,4 +92,24 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { private void writeResponse(ChannelHandlerContext ctx, ClusterResponse response) { ctx.writeAndFlush(response); } + + private void handlePingRequest(ChannelHandlerContext ctx, ClusterRequest request) { + if (request.getData() == null || StringUtil.isBlank((String)request.getData())) { + writeBadResponse(ctx, request); + return; + } + String namespace = (String)request.getData(); + String clientAddress = getRemoteAddress(ctx); + // Add the remote namespace to connection manager. + int curCount = ConnectionManager.addConnection(namespace, clientAddress).getConnectedCount(); + int status = ClusterConstants.RESPONSE_STATUS_OK; + ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); + writeResponse(ctx, response); + + RecordLog.info("[TokenServerHandler] Client <{0}> registered with namespace <{1}>", clientAddress, namespace); + } + + private String getRemoteAddress(ChannelHandlerContext ctx) { + return ctx.channel().remoteAddress().toString(); + } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java new file mode 100644 index 00000000..82b3bdc3 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/init/DefaultClusterServerInitFunc.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.init; + +import com.alibaba.csp.sentinel.cluster.ClusterConstants; +import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; +import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.FlowResponseDataWriter; +import com.alibaba.csp.sentinel.cluster.server.codec.data.ParamFlowRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.PingRequestDataDecoder; +import com.alibaba.csp.sentinel.cluster.server.codec.data.PingResponseDataWriter; +import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry; +import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry; +import com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessorProvider; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.log.RecordLog; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public class DefaultClusterServerInitFunc implements InitFunc { + + @Override + public void init() throws Exception { + initDefaultEntityDecoders(); + initDefaultEntityWriters(); + + initDefaultProcessors(); + + // Eagerly-trigger the SPI pre-load of token service. + TokenServiceProvider.getService(); + + RecordLog.info("[DefaultClusterServerInitFunc] Default entity codec and processors registered"); + } + + private void initDefaultEntityWriters() { + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PING, new PingResponseDataWriter()); + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_FLOW, new FlowResponseDataWriter()); + ResponseDataWriterRegistry.addWriter(ClusterConstants.MSG_TYPE_PARAM_FLOW, new FlowResponseDataWriter()); + } + + private void initDefaultEntityDecoders() { + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PING, new PingRequestDataDecoder()); + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_FLOW, new FlowRequestDataDecoder()); + RequestDataDecodeRegistry.addDecoder(ClusterConstants.MSG_TYPE_PARAM_FLOW, new ParamFlowRequestDataDecoder()); + } + + private void initDefaultProcessors() { + // Eagerly-trigger the SPI pre-load. + RequestProcessorProvider.getProcessor(0); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java new file mode 100644 index 00000000..f97441ca --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/log/ClusterServerStatLogUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.server.log; + +import com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterServerStatLogUtil { + + private static final String FILE_NAME = "sentinel-server.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-server-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterServerStatLogUtil() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java index 827960ae..d08a84b3 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java @@ -15,8 +15,10 @@ */ package com.alibaba.csp.sentinel.cluster.server.processor; +import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenService; +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; @@ -27,6 +29,7 @@ import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; * @author Eric Zhao * @since 1.4.0 */ +@RequestType(ClusterConstants.MSG_TYPE_FLOW) public class FlowRequestProcessor implements RequestProcessor { @Override diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java index 37bdf159..888fc2b5 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java @@ -17,8 +17,10 @@ package com.alibaba.csp.sentinel.cluster.server.processor; import java.util.Collection; +import com.alibaba.csp.sentinel.cluster.ClusterConstants; import com.alibaba.csp.sentinel.cluster.TokenResult; import com.alibaba.csp.sentinel.cluster.TokenService; +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.cluster.request.ClusterRequest; import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData; import com.alibaba.csp.sentinel.cluster.response.ClusterResponse; @@ -29,6 +31,7 @@ import com.alibaba.csp.sentinel.cluster.server.TokenServiceProvider; * @author Eric Zhao * @since 1.4.0 */ +@RequestType(ClusterConstants.MSG_TYPE_PARAM_FLOW) public class ParamFlowRequestProcessor implements RequestProcessor { @Override diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java similarity index 55% rename from sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java rename to sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java index 87aa6bae..bd572f27 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorProvider.java @@ -16,23 +16,49 @@ package com.alibaba.csp.sentinel.cluster.server.processor; import java.util.Map; +import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; +import com.alibaba.csp.sentinel.cluster.annotation.RequestType; import com.alibaba.csp.sentinel.util.AssertUtil; /** * @author Eric Zhao * @since 1.4.0 */ -public final class RequestProcessorRegistry { +public final class RequestProcessorProvider { private static final Map PROCESSOR_MAP = new ConcurrentHashMap<>(); + private static final ServiceLoader SERVICE_LOADER = ServiceLoader.load(RequestProcessor.class); + + static { + loadAndInit(); + } + + private static void loadAndInit() { + for (RequestProcessor processor : SERVICE_LOADER) { + Integer type = parseRequestType(processor); + if (type != null) { + PROCESSOR_MAP.put(type, processor); + } + } + } + + private static Integer parseRequestType(RequestProcessor processor) { + RequestType requestType = processor.getClass().getAnnotation(RequestType.class); + if (requestType != null) { + return requestType.value(); + } else { + return null; + } + } + public static RequestProcessor getProcessor(int type) { return PROCESSOR_MAP.get(type); } - public static void addProcessorIfAbsent(int type, RequestProcessor processor) { + static void addProcessorIfAbsent(int type, RequestProcessor processor) { // TBD: use putIfAbsent in JDK 1.8. if (PROCESSOR_MAP.containsKey(type)) { return; @@ -40,10 +66,11 @@ public final class RequestProcessorRegistry { PROCESSOR_MAP.put(type, processor); } - public static void addProcessor(int type, RequestProcessor processor) { + static void addProcessor(int type, RequestProcessor processor) { AssertUtil.notNull(processor, "processor cannot be null"); PROCESSOR_MAP.put(type, processor); } - private RequestProcessorRegistry() {} + + private RequestProcessorProvider() {} } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java index d226b04d..485f2adc 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java @@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.cluster.server.util; /** * @author Eric Zhao + * @since 1.4.0 */ public final class ClusterRuleUtil { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer new file mode 100644 index 00000000..ccfb81e4 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.DefaultEmbeddedTokenServer \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor new file mode 100644 index 00000000..991b781d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.server.processor.RequestProcessor @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.cluster.server.processor.FlowRequestProcessor +com.alibaba.csp.sentinel.cluster.server.processor.ParamFlowRequestProcessor \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..95b8c9db --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.cluster.server.init.DefaultClusterServerInitFunc \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java new file mode 100644 index 00000000..cfb0e7d9 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/ClusterFlowTestUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster; + +import static org.junit.Assert.*; + +/** + * Useful for testing clustered flow control. + * Only used for test. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterFlowTestUtil { + + public static void assertResultPass(TokenResult result) { + assertNotNull(result); + assertEquals(TokenResultStatus.OK, (int) result.getStatus()); + } + + public static void assertResultBlock(TokenResult result) { + assertNotNull(result); + assertEquals(TokenResultStatus.BLOCKED, (int) result.getStatus()); + } + + public static void assertResultWait(TokenResult result, int waitInMs) { + assertNotNull(result); + assertEquals(TokenResultStatus.SHOULD_WAIT, (int) result.getStatus()); + assertEquals(waitInMs, result.getWaitInMs()); + } + + public static void sleep(int t) { + try { + Thread.sleep(t); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private ClusterFlowTestUtil() {} +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java new file mode 100644 index 00000000..2b062e69 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow; + +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics; +import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; + +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; +import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@Ignore +public class ClusterFlowCheckerTest { + + @Test + public void testAcquireClusterTokenOccupyPass() { + long flowId = 98765L; + final int threshold = 5; + FlowRule clusterRule = new FlowRule("abc") + .setCount(threshold) + .setClusterMode(true) + .setClusterConfig(new ClusterFlowConfig() + .setFlowId(flowId) + .setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL)); + int sampleCount = 5; + int intervalInMs = 1000; + int bucketLength = intervalInMs / sampleCount; + ClusterMetric metric = new ClusterMetric(sampleCount, intervalInMs); + ClusterMetricStatistics.putMetric(flowId, metric); + + System.out.println(System.currentTimeMillis()); + assertResultPass(tryAcquire(clusterRule, false)); + assertResultPass(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, true)); + assertResultPass(tryAcquire(clusterRule, false)); + assertResultBlock(tryAcquire(clusterRule, true)); + sleep(bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + assertResultBlock(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + assertResultWait(tryAcquire(clusterRule, true), bucketLength); + assertResultBlock(tryAcquire(clusterRule, false)); + sleep(bucketLength); + assertResultPass(tryAcquire(clusterRule, false)); + + ClusterMetricStatistics.removeMetric(flowId); + } + + private TokenResult tryAcquire(FlowRule clusterRule, boolean occupy) { + return ClusterFlowChecker.acquireClusterToken(clusterRule, 1, occupy); + } +} \ No newline at end of file From 2d1313f220a57242ae37aad0b5e3e87032d265f1 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 22:20:43 +0800 Subject: [PATCH 17/20] Update test dependency of sentinel-cluster-server-default Signed-off-by: Eric Zhao --- .../sentinel-cluster-server-default/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sentinel-cluster/sentinel-cluster-server-default/pom.xml b/sentinel-cluster/sentinel-cluster-server-default/pom.xml index b240ed51..cce6b03c 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-server-default/pom.xml @@ -35,5 +35,16 @@ io.netty netty-all + + + junit + junit + test + + + org.mockito + mockito-core + test + \ No newline at end of file From a5819e092dafaa097606fb37bc7cd8256276882b Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 22:56:56 +0800 Subject: [PATCH 18/20] Add HTTP command for modifying global state and client config - Add several command handlers - Update cluster state manager Signed-off-by: Eric Zhao --- .../FetchClusterClientConfigHandler.java | 42 +++++++++++++++ .../ModifyClusterClientConfigHandler.java | 26 +++++++--- ...libaba.csp.sentinel.command.CommandHandler | 3 +- .../sentinel/cluster/ClusterStateManager.java | 33 +++++++----- .../sentinel-datasource-nacos/pom.xml | 2 +- .../FetchClusterModeCommandHandler.java | 51 +++++++++++++++++++ .../ModifyClusterModeCommandHandler.java | 46 +++++++++++++++++ ...libaba.csp.sentinel.command.CommandHandler | 4 +- 8 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java create mode 100644 sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java create mode 100644 sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java new file mode 100644 index 00000000..407d7311 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterClientConfigHandler.java @@ -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.command.handler; + +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; +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.JSON; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "cluster/client/fetchConfig") +public class FetchClusterClientConfigHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + ClusterClientConfig config = new ClusterClientConfig() + .setServerHost(ClusterClientConfigManager.getServerHost()) + .setServerPort(ClusterClientConfigManager.getServerPort()) + .setRequestTimeout(ClusterClientConfigManager.getRequestTimeout()); + return CommandResponse.ofSuccess(JSON.toJSONString(config)); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java index 5cc5b4f8..8614fb8f 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java @@ -15,28 +15,42 @@ */ package com.alibaba.csp.sentinel.command.handler; +import java.net.URLDecoder; + import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; 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 = "modifyClusterConfig") +@CommandMapping(name = "cluster/client/modifyConfig") public class ModifyClusterClientConfigHandler implements CommandHandler { @Override public CommandResponse 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("[ModifyClusterClientConfigHandler] Receiving cluster client config: " + data); + ClusterClientConfig clusterClientConfig = JSON.parseObject(data, ClusterClientConfig.class); + ClusterClientConfigManager.applyNewConfig(clusterClientConfig); - // TODO: parse the new config; - ClusterClientConfig clusterClientConfig = null; - ClusterClientConfigManager.applyNewConfig(clusterClientConfig); - - return CommandResponse.ofSuccess("ok"); + return CommandResponse.ofSuccess("ok"); + } catch (Exception e) { + RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); + return CommandResponse.ofFailure(e, "decode client cluster config error"); + } } } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler index a0af73b5..80d3582c 100755 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -1 +1,2 @@ -com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler \ No newline at end of file +com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler +com.alibaba.csp.sentinel.command.handler.FetchClusterClientConfigHandler \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java index 2800b2f6..2a27c745 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java @@ -43,6 +43,8 @@ public final class ClusterStateManager { private static volatile SentinelProperty stateProperty = new DynamicSentinelProperty(); private static final PropertyListener PROPERTY_LISTENER = new ClusterStatePropertyListener(); + private static final Object UPDATE_LOCK = new Object(); + static { InitExecutor.doInit(); stateProperty.addListener(PROPERTY_LISTENER); @@ -75,9 +77,9 @@ public final class ClusterStateManager { * it will be turned off. Then the cluster client will be started. *

    */ - public static void setToClient() { + public static boolean setToClient() { if (mode == CLUSTER_CLIENT) { - return; + return true; } mode = CLUSTER_CLIENT; sleepIfNeeded(); @@ -91,11 +93,14 @@ public final class ClusterStateManager { if (tokenClient != null) { tokenClient.start(); RecordLog.info("[ClusterStateManager] Changing cluster mode to client"); + return true; } else { RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)"); + return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex); + return false; } } @@ -105,9 +110,9 @@ public final class ClusterStateManager { * it will be turned off. Then the cluster server will be started. *

    */ - public static void setToServer() { + public static boolean setToServer() { if (mode == CLUSTER_SERVER) { - return; + return true; } mode = CLUSTER_SERVER; sleepIfNeeded(); @@ -121,11 +126,14 @@ public final class ClusterStateManager { if (server != null) { server.start(); RecordLog.info("[ClusterStateManager] Changing cluster mode to server"); + return true; } else { RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)"); + return false; } } catch (Exception ex) { RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex); + return false; } } @@ -163,20 +171,21 @@ public final class ClusterStateManager { public void configUpdate(Integer value) { applyState(value); } + } - private synchronized void applyState(Integer state) { - if (state == null || state < 0) { - return; - } + public static boolean applyState(Integer state) { + if (state == null || state < 0) { + return false; + } + synchronized (UPDATE_LOCK) { switch (state) { case CLUSTER_CLIENT: - setToClient(); - break; + return setToClient(); case CLUSTER_SERVER: - setToServer(); - break; + return setToServer(); default: RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state); + return false; } } } diff --git a/sentinel-extension/sentinel-datasource-nacos/pom.xml b/sentinel-extension/sentinel-datasource-nacos/pom.xml index 54f686d5..dd0b0444 100644 --- a/sentinel-extension/sentinel-datasource-nacos/pom.xml +++ b/sentinel-extension/sentinel-datasource-nacos/pom.xml @@ -13,7 +13,7 @@ jar - 0.4.0 + 0.2.1 diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java new file mode 100644 index 00000000..11eac68f --- /dev/null +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java @@ -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.command.handler; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +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.JSONObject; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "getClusterMode") +public class FetchClusterModeCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + JSONObject res = new JSONObject() + .fluentPut("mode", ClusterStateManager.getMode()) + .fluentPut("lastModified", ClusterStateManager.getLastModified()) + .fluentPut("clientAvailable", isClusterClientSpiAvailable()) + .fluentPut("serverAvailable", isClusterServerSpiAvailable()); + return CommandResponse.ofSuccess(res.toJSONString()); + } + + private boolean isClusterClientSpiAvailable() { + return TokenClientProvider.getClient() != null; + } + + private boolean isClusterServerSpiAvailable() { + return EmbeddedClusterTokenServerProvider.getServer() != null; + } +} diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java new file mode 100644 index 00000000..1d67292e --- /dev/null +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.command.handler; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +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; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +@CommandMapping(name = "setClusterMode") +public class ModifyClusterModeCommandHandler implements CommandHandler { + + @Override + public CommandResponse handle(CommandRequest request) { + try { + int mode = Integer.valueOf(request.getParam("mode")); + if (ClusterStateManager.applyState(mode)) { + return CommandResponse.ofSuccess("success"); + } else { + return CommandResponse.ofSuccess("failed"); + } + } catch (NumberFormatException ex) { + return CommandResponse.ofFailure(new IllegalArgumentException("invalid mode")); + } catch (Exception ex) { + return CommandResponse.ofFailure(ex); + } + } +} diff --git a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler index 1dd95c88..4cd2606e 100755 --- a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler +++ b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -11,4 +11,6 @@ com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler -com.alibaba.csp.sentinel.command.handler.VersionCommandHandler \ No newline at end of file +com.alibaba.csp.sentinel.command.handler.VersionCommandHandler +com.alibaba.csp.sentinel.command.handler.FetchClusterModeCommandHandler +com.alibaba.csp.sentinel.command.handler.ModifyClusterModeCommandHandler \ No newline at end of file From 2735954afd7283d502a12bfaf0209656955a65c3 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Fri, 14 Dec 2018 00:15:17 +0800 Subject: [PATCH 19/20] Update cluster related command APIs and some enhancement and fix for cluster module Signed-off-by: Eric Zhao --- .codecov.yml | 3 +- sentinel-cluster/README.md | 7 + .../client/DefaultClusterTokenClient.java | 10 +- .../config/ClusterClientConfigManager.java | 1 + .../client/handler/TokenClientHandler.java | 5 +- .../ModifyClusterClientConfigHandler.java | 2 +- .../flow/rule/ClusterFlowRuleManager.java | 59 +++++++++ .../rule/ClusterParamFlowRuleManager.java | 49 +++++++ .../server/SentinelDefaultTokenServer.java | 16 +-- .../FetchClusterFlowRulesCommandHandler.java | 42 ++++++ ...chClusterParamFlowRulesCommandHandler.java | 42 ++++++ .../FetchClusterServerConfigHandler.java | 71 ++++++++++ .../FetchClusterServerInfoCommandHandler.java | 69 ++++++++++ .../ModifyClusterFlowRulesCommandHandler.java | 63 +++++++++ ...fyClusterParamFlowRulesCommandHandler.java | 63 +++++++++ .../ModifyClusterServerFlowConfigHandler.java | 64 +++++++++ ...fyClusterServerTransportConfigHandler.java | 56 ++++++++ .../ModifyServerNamespaceSetHandler.java | 56 ++++++++ .../config/ClusterServerConfigManager.java | 123 +++++++++++------- .../server/connection/ConnectionManager.java | 6 + .../server/handler/TokenServerHandler.java | 5 - ...libaba.csp.sentinel.command.CommandHandler | 9 ++ .../cluster/flow/ClusterFlowCheckerTest.java | 3 +- .../sentinel/cluster/ClusterStateManager.java | 51 +++++++- .../block/flow/TrafficShapingController.java | 10 ++ .../flow/controller/DefaultController.java | 12 ++ .../controller/RateLimiterController.java | 5 + .../flow/controller/WarmUpController.java | 5 + .../WarmUpRateLimiterController.java | 5 + .../slots/statistic/data/MetricBucket.java | 49 ++++--- .../FetchClusterModeCommandHandler.java | 2 +- .../ModifyClusterModeCommandHandler.java | 4 +- ...libaba.csp.sentinel.command.CommandHandler | 4 +- 33 files changed, 869 insertions(+), 102 deletions(-) create mode 100644 sentinel-cluster/README.md create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java create mode 100755 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler rename sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/{ => cluster}/FetchClusterModeCommandHandler.java (97%) rename sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/{ => cluster}/ModifyClusterModeCommandHandler.java (89%) diff --git a/.codecov.yml b/.codecov.yml index 33e7d838..b15b071e 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,4 +1,5 @@ ignore: - "sentinel-demo/.*" - "sentinel-dashboard/.*" - - "sentinel-benchmark/.*" \ No newline at end of file + - "sentinel-benchmark/.*" + - "sentinel-transport/.*" \ No newline at end of file diff --git a/sentinel-cluster/README.md b/sentinel-cluster/README.md new file mode 100644 index 00000000..d6e1c2fd --- /dev/null +++ b/sentinel-cluster/README.md @@ -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 \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java index d2bc0045..58779cec 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -55,7 +55,6 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { changeServer(clusterClientConfig); } }); - // TODO: check here, who should start the client? initNewConnection(); } @@ -90,7 +89,6 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { return; } try { - // TODO: what if the client is pending init? if (transportClient != null) { transportClient.stop(); } @@ -108,12 +106,14 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { if (shouldStart.get()) { if (transportClient != null) { transportClient.start(); + } else { + RecordLog.warn("[DefaultClusterTokenClient] Cannot start transport client: client not created"); } } } private void stopClientIfStarted() throws Exception { - if (shouldStart.get()) { + if (shouldStart.compareAndSet(true, false)) { if (transportClient != null) { transportClient.stop(); } @@ -129,9 +129,7 @@ public class DefaultClusterTokenClient implements ClusterTokenClient { @Override public void stop() throws Exception { - if (shouldStart.compareAndSet(true, false)) { - stopClientIfStarted(); - } + stopClientIfStarted(); } @Override diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java index 834bdcd7..ca2af3da 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java @@ -104,6 +104,7 @@ public final class ClusterClientConfigManager { public static boolean isValidConfig(ClusterClientConfig config) { return config != null && StringUtil.isNotBlank(config.getServerHost()) && config.getServerPort() > 0 + && config.getServerPort() <= 65535 && config.getRequestTimeout() > 0; } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java index 3d36b4d8..61b2e37c 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -45,14 +45,13 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { @Override 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); RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress()); } @Override 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) { ClusterResponse response = (ClusterResponse) msg; @@ -96,7 +95,7 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 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(); } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java index 8614fb8f..73d00d5d 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java @@ -46,7 +46,7 @@ public class ModifyClusterClientConfigHandler implements CommandHandler ClusterClientConfig clusterClientConfig = JSON.parseObject(data, ClusterClientConfig.class); ClusterClientConfigManager.applyNewConfig(clusterClientConfig); - return CommandResponse.ofSuccess("ok"); + return CommandResponse.ofSuccess("success"); } catch (Exception e) { RecordLog.warn("[ModifyClusterClientConfigHandler] Decode client cluster config error", e); return CommandResponse.ofFailure(e, "decode client cluster config error"); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java index 27b6853b..621ff6b5 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterFlowRuleManager.java @@ -15,6 +15,7 @@ */ package com.alibaba.csp.sentinel.cluster.flow.rule; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; 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) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); 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) { if (!ClusterRuleUtil.validId(id)) { return null; @@ -196,10 +208,57 @@ public final class ClusterFlowRuleManager { return FLOW_RULES.get(id); } + public static List 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 getFlowRules(String namespace) { + if (StringUtil.isEmpty(namespace)) { + return new ArrayList<>(); + } + List rules = new ArrayList<>(); + Set 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 rules) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); + if (property != null) { + property.getProperty().updateValue(rules); + } + } + private static void resetNamespaceFlowIdMapFor(/*@Valid*/ String namespace) { NAMESPACE_FLOW_ID_MAP.put(namespace, new HashSet()); } + /** + * Clear all rules of the provided namespace and reset map. + * + * @param namespace valid namespace + */ private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { Set flowIdSet = NAMESPACE_FLOW_ID_MAP.get(namespace); if (flowIdSet != null && !flowIdSet.isEmpty()) { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java index d6b255bc..c81393d5 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java @@ -15,6 +15,7 @@ */ package com.alibaba.csp.sentinel.cluster.flow.rule; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -217,6 +218,54 @@ public final class ClusterParamFlowRuleManager { return PARAM_RULES.get(id); } + public static List 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 getParamRules(String namespace) { + if (StringUtil.isEmpty(namespace)) { + return new ArrayList<>(); + } + List rules = new ArrayList<>(); + Set 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 rules) { + AssertUtil.notEmpty(namespace, "namespace cannot be empty"); + NamespaceFlowProperty property = PROPERTY_MAP.get(namespace); + if (property != null) { + property.getProperty().updateValue(rules); + } + } + + /** + * Get connected count for associated namespace of given {@code flowId}. + * + * @param flowId existing rule ID + * @return connected count + */ public static int getConnectedCount(long flowId) { if (flowId <= 0) { return 0; diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java index c6a42db9..7aa478fe 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/SentinelDefaultTokenServer.java @@ -79,7 +79,7 @@ public class SentinelDefaultTokenServer implements ClusterTokenServer { } try { if (server != null) { - stopServerIfStarted(); + stopServer(); } this.server = new NettyTransportServer(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 public void stop() throws Exception { if (shouldStart.compareAndSet(true, false)) { - stopServerIfStarted(); + stopServer(); } } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java new file mode 100644 index 00000000..359dfc18 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterFlowRulesCommandHandler.java @@ -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 { + + @Override + public CommandResponse 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))); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java new file mode 100644 index 00000000..5f490411 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterParamFlowRulesCommandHandler.java @@ -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 { + + @Override + public CommandResponse 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))); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java new file mode 100644 index 00000000..72b94640 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerConfigHandler.java @@ -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 { + + @Override + public CommandResponse handle(CommandRequest request) { + String namespace = request.getParam("namespace"); + if (StringUtil.isEmpty(namespace)) { + return globalConfigResult(); + } + return namespaceConfigResult(namespace); + } + + private CommandResponse 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 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()); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java new file mode 100644 index 00000000..4996be25 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/FetchClusterServerInfoCommandHandler.java @@ -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 { + + @Override + public CommandResponse handle(CommandRequest request) { + JSONObject info = new JSONObject(); + JSONArray connectionGroups = new JSONArray(); + Set 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()); + } +} + diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java new file mode 100644 index 00000000..08882c09 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java @@ -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 { + + @Override + public CommandResponse 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 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"; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java new file mode 100644 index 00000000..b4632173 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java @@ -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 { + + @Override + public CommandResponse 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 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"; +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java new file mode 100644 index 00000000..9e444666 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java @@ -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 { + + @Override + public CommandResponse 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"); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java new file mode 100644 index 00000000..f1e267db --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerTransportConfigHandler.java @@ -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 { + + @Override + public CommandResponse 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")); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java new file mode 100644 index 00000000..4a78600f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java @@ -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 { + + @Override + public CommandResponse 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 set = JSON.parseObject(data, new TypeReference>() {}); + 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"); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java index 4054d824..92c8319a 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java @@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.cluster.server.config; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -110,6 +111,35 @@ public final class ClusterServerConfigManager { } } + public static void loadServerNamespaceSet(Set 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> { @Override @@ -137,6 +167,8 @@ public final class ClusterServerConfigManager { ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); return; } + + newSet = new HashSet<>(newSet); newSet.add(ServerConstants.DEFAULT_NAMESPACE); Set oldSet = ClusterServerConfigManager.namespaceSet; @@ -186,48 +218,7 @@ public final class ClusterServerConfigManager { } } - private static class ServerGlobalFlowPropertyListener implements PropertyListener { - - @Override - public void configUpdate(ServerFlowConfig config) { - applyGlobalFlowConfig(config); - } - - @Override - public void configLoad(ServerFlowConfig 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(); - } - } - } - - public static void updateTokenServer(ServerTransportConfig config) { + private static void updateTokenServer(ServerTransportConfig config) { int newPort = config.getPort(); AssertUtil.isTrue(newPort > 0, "token server port should be valid (positive)"); if (newPort == port) { @@ -240,19 +231,55 @@ public final class ClusterServerConfigManager { } } + private static class ServerGlobalFlowPropertyListener implements PropertyListener { + + @Override + public void configUpdate(ServerFlowConfig config) { + applyGlobalFlowConfig(config); + } + + @Override + public void configLoad(ServerFlowConfig config) { + applyGlobalFlowConfig(config); + } + + private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { + if (!isValidFlowConfig(config)) { + RecordLog.warn( + "[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: " + config); + return; + } + RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: " + config); + if (config.getExceedCount() != exceedCount) { + exceedCount = config.getExceedCount(); + } + if (config.getMaxOccupyRatio() != maxOccupyRatio) { + maxOccupyRatio = config.getMaxOccupyRatio(); + } + int newIntervalMs = config.getIntervalMs(); + int newSampleCount = config.getSampleCount(); + if (newIntervalMs != intervalMs || newSampleCount != sampleCount) { + if (newIntervalMs <= 0 || newSampleCount <= 0 || newIntervalMs % newSampleCount != 0) { + RecordLog.warn("[ClusterServerConfigManager] Ignoring invalid flow interval or sample count"); + } else { + intervalMs = newIntervalMs; + sampleCount = newSampleCount; + // Reset all the metrics. + ClusterMetricStatistics.resetFlowMetrics(); + ClusterParamMetricStatistics.resetFlowMetrics(); + } + } + } + } + public static boolean isValidTransportConfig(ServerTransportConfig config) { - return config != null && config.getPort() > 0; + return config != null && config.getPort() > 0 && config.getPort() <= 65535; } public static boolean isValidFlowConfig(ServerFlowConfig config) { 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) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); ServerFlowConfig config = NAMESPACE_CONF.get(namespace); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java index 8ce244fe..5bbbaeaa 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java @@ -100,6 +100,12 @@ public final class ConnectionManager { 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 ConnectionManager() {} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java index 69d113cb..b230f30c 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java @@ -46,13 +46,11 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { public void channelActive(ChannelHandlerContext ctx) throws Exception { globalConnectionPool.createConnection(ctx.channel()); String remoteAddress = getRemoteAddress(ctx); - System.out.println("[TokenServerHandler] Connection established, remote client address: " + remoteAddress); //TODO: DEBUG } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { String remoteAddress = getRemoteAddress(ctx); - System.out.println("[TokenServerHandler] Connection inactive, remote client address: " + remoteAddress); //TODO: DEBUG globalConnectionPool.remove(ctx.channel()); ConnectionManager.removeConnection(remoteAddress); } @@ -61,7 +59,6 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { globalConnectionPool.refreshLastReadTime(ctx.channel()); - System.out.println(String.format("[%s] Server message recv: %s", System.currentTimeMillis(), msg)); //TODO: DEBUG if (msg instanceof ClusterRequest) { ClusterRequest request = (ClusterRequest)msg; @@ -105,8 +102,6 @@ public class TokenServerHandler extends ChannelInboundHandlerAdapter { int status = ClusterConstants.RESPONSE_STATUS_OK; ClusterResponse response = new ClusterResponse<>(request.getId(), request.getType(), status, curCount); writeResponse(ctx, response); - - RecordLog.info("[TokenServerHandler] Client <{0}> registered with namespace <{1}>", clientAddress, namespace); } private String getRemoteAddress(ChannelHandlerContext ctx) { diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler new file mode 100755 index 00000000..5fe1f5b8 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -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 \ No newline at end of file diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java index 2b062e69..970e9fca 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowCheckerTest.java @@ -32,10 +32,9 @@ import static com.alibaba.csp.sentinel.cluster.ClusterFlowTestUtil.*; * @author Eric Zhao * @since 1.4.0 */ -@Ignore public class ClusterFlowCheckerTest { - @Test + //@Test public void testAcquireClusterTokenOccupyPass() { long flowId = 98765L; final int threshold = 5; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java index 2a27c745..4a4b1334 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java @@ -84,6 +84,10 @@ public final class ClusterStateManager { mode = CLUSTER_CLIENT; sleepIfNeeded(); lastModified = TimeUtil.currentTimeMillis(); + return startClient(); + } + + private static boolean startClient() { try { EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); 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; + } + } + /** *

    * Set current mode to server mode. If Sentinel currently works in client mode, @@ -117,6 +138,10 @@ public final class ClusterStateManager { mode = CLUSTER_SERVER; sleepIfNeeded(); lastModified = TimeUtil.currentTimeMillis(); + return startServer(); + } + + private static boolean startServer() { try { ClusterTokenClient tokenClient = TokenClientProvider.getClient(); 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). * Or we need to wait for a while. @@ -163,12 +205,12 @@ public final class ClusterStateManager { private static class ClusterStatePropertyListener implements PropertyListener { @Override - public void configLoad(Integer value) { + public synchronized void configLoad(Integer value) { applyState(value); } @Override - public void configUpdate(Integer value) { + public synchronized void configUpdate(Integer value) { applyState(value); } } @@ -177,6 +219,9 @@ public final class ClusterStateManager { if (state == null || state < 0) { return false; } + if (state == mode) { + return true; + } synchronized (UPDATE_LOCK) { switch (state) { 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; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java index bcba4a18..6c7a60a2 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java @@ -24,6 +24,16 @@ import com.alibaba.csp.sentinel.node.Node; */ 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. * diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java index 4dc8368f..e3289cd7 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java @@ -36,6 +36,11 @@ public class DefaultController implements TrafficShapingController { @Override 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); if (curCount + acquireCount > count) { return false; @@ -51,4 +56,11 @@ public class DefaultController implements TrafficShapingController { return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)node.passQps(); } + private void sleep(int timeMillis) { + try { + Thread.sleep(timeMillis); + } catch (InterruptedException e) { + // Ignore. + } + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java index f1dc74af..e17e53cf 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java @@ -39,6 +39,11 @@ public class RateLimiterController implements TrafficShapingController { @Override 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(); // Calculate the interval between every two requests. long costTime = Math.round(1.0 * (acquireCount) / count * 1000); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java index 08c71166..2cc99a15 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java @@ -107,6 +107,11 @@ public class WarmUpController implements TrafficShapingController { @Override 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 previousQps = node.previousPassQps(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java index 8c2c20dd..7080f4c4 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java @@ -39,6 +39,11 @@ public class WarmUpRateLimiterController extends WarmUpController { @Override 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(); syncToken(previousQps); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java index 0e96a28c..5648cb0f 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java @@ -16,6 +16,7 @@ package com.alibaba.csp.sentinel.slots.statistic.data; import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; 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 { - 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; public MetricBucket() { + MetricEvent[] events = MetricEvent.values(); + this.counters = new LongAdder[events.length]; + for (MetricEvent event : events) { + counters[event.ordinal()] = new LongAdder(); + } initMinRt(); } @@ -48,29 +50,36 @@ public class MetricBucket { * @return new metric bucket in initial state */ public MetricBucket reset() { - pass.reset(); - block.reset(); - exception.reset(); - rt.reset(); - success.reset(); + for (MetricEvent event : MetricEvent.values()) { + counters[event.ordinal()].reset(); + } initMinRt(); 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() { - return pass.sum(); + return get(MetricEvent.PASS); } public long block() { - return block.sum(); + return get(MetricEvent.BLOCK); } public long exception() { - return exception.sum(); + return get(MetricEvent.EXCEPTION); } public long rt() { - return rt.sum(); + return get(MetricEvent.RT); } public long minRt() { @@ -78,27 +87,27 @@ public class MetricBucket { } public long success() { - return success.sum(); + return get(MetricEvent.SUCCESS); } public void addPass() { - pass.add(1L); + add(MetricEvent.PASS, 1); } public void addException() { - exception.add(1L); + add(MetricEvent.EXCEPTION, 1); } public void addBlock() { - block.add(1L); + add(MetricEvent.BLOCK, 1); } public void addSuccess() { - success.add(1L); + add(MetricEvent.SUCCESS, 1); } public void addRT(long rt) { - this.rt.add(rt); + add(MetricEvent.RT, rt); // Not thread-safe, but it's okay. if (rt < minRt) { diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java similarity index 97% rename from sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java rename to sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java index 11eac68f..14be5199 100644 --- a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/FetchClusterModeCommandHandler.java +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/FetchClusterModeCommandHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * 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.client.TokenClientProvider; diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java similarity index 89% rename from sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java rename to sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java index 1d67292e..072a52b0 100644 --- a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterModeCommandHandler.java +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/command/handler/cluster/ModifyClusterModeCommandHandler.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * 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.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; /** * @author Eric Zhao @@ -32,6 +33,7 @@ public class ModifyClusterModeCommandHandler implements CommandHandler { public CommandResponse handle(CommandRequest request) { try { int mode = Integer.valueOf(request.getParam("mode")); + RecordLog.info("[ModifyClusterModeCommandHandler] Modifying cluster mode to: " + mode); if (ClusterStateManager.applyState(mode)) { return CommandResponse.ofSuccess("success"); } else { diff --git a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler index 4cd2606e..2edadbd4 100755 --- a/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler +++ b/sentinel-transport/sentinel-transport-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler @@ -12,5 +12,5 @@ com.alibaba.csp.sentinel.command.handler.OnOffGetCommandHandler com.alibaba.csp.sentinel.command.handler.OnOffSetCommandHandler com.alibaba.csp.sentinel.command.handler.SendMetricCommandHandler com.alibaba.csp.sentinel.command.handler.VersionCommandHandler -com.alibaba.csp.sentinel.command.handler.FetchClusterModeCommandHandler -com.alibaba.csp.sentinel.command.handler.ModifyClusterModeCommandHandler \ No newline at end of file +com.alibaba.csp.sentinel.command.handler.cluster.FetchClusterModeCommandHandler +com.alibaba.csp.sentinel.command.handler.cluster.ModifyClusterModeCommandHandler \ No newline at end of file From f01fe2b437191fb405a3c159c068abd9822c12ae Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Sun, 9 Dec 2018 21:56:22 +0800 Subject: [PATCH 20/20] Add demo module for Sentinel cluster flow control Signed-off-by: Eric Zhao --- sentinel-demo/pom.xml | 1 + sentinel-demo/sentinel-demo-cluster/pom.xml | 44 ++++++++++++ .../demo/cluster/ClusterClientDemo.java | 61 ++++++++++++++++ .../demo/cluster/ClusterServerDemo.java | 50 +++++++++++++ .../sentinel/demo/cluster/DemoConstants.java | 29 ++++++++ .../init/DemoClusterClientInitFunc.java | 57 +++++++++++++++ .../init/DemoClusterServerInitFunc.java | 71 +++++++++++++++++++ .../com.alibaba.csp.sentinel.init.InitFunc | 2 + 8 files changed, 315 insertions(+) create mode 100644 sentinel-demo/sentinel-demo-cluster/pom.xml create mode 100644 sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java create mode 100644 sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java create mode 100644 sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java create mode 100644 sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java create mode 100644 sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java create mode 100755 sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc diff --git a/sentinel-demo/pom.xml b/sentinel-demo/pom.xml index 3ab45f6f..2cc6f9ad 100755 --- a/sentinel-demo/pom.xml +++ b/sentinel-demo/pom.xml @@ -28,6 +28,7 @@ sentinel-demo-annotation-spring-aop sentinel-demo-parameter-flow-control sentinel-demo-slot-chain-spi + sentinel-demo-cluster diff --git a/sentinel-demo/sentinel-demo-cluster/pom.xml b/sentinel-demo/sentinel-demo-cluster/pom.xml new file mode 100644 index 00000000..e76d8bb5 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/pom.xml @@ -0,0 +1,44 @@ + + + + sentinel-demo + com.alibaba.csp + 1.4.0-SNAPSHOT + + 4.0.0 + + sentinel-demo-cluster + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.alibaba.csp + sentinel-parameter-flow-control + + + + com.alibaba.csp + sentinel-cluster-client-default + ${project.version} + + + com.alibaba.csp + sentinel-cluster-server-default + ${project.version} + + + + com.alibaba.csp + sentinel-datasource-nacos + + + \ No newline at end of file diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java new file mode 100644 index 00000000..c665fc3a --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java @@ -0,0 +1,61 @@ +/* + * 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.demo.cluster; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + *

    Run this demo with the following args: -Dproject.name=appA

    + * + * @author Eric Zhao + */ +public class ClusterClientDemo { + + public static void main(String[] args) { + InitExecutor.doInit(); + + // Manually schedule the cluster mode to client. + // In common, we need a scheduling system to modify the cluster mode automatically. + // Command HTTP API: http://:/setClusterMode?mode= + ClusterStateManager.setToClient(); + + String resourceName = "cluster-demo-entry"; + + // Assume we have a cluster flow rule for `demo-resource`: QPS = 5 in AVG_LOCAL mode. + for (int i = 0; i < 10; i++) { + tryEntry(resourceName); + } + } + + private static void tryEntry(String res) { + Entry entry = null; + try { + entry = SphU.entry(res, EntryType.IN, 1, "abc", "def"); + System.out.println("Passed"); + } catch (BlockException ex) { + ex.printStackTrace(); + } finally { + if (entry != null) { + entry.exit(); + } + } + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java new file mode 100644 index 00000000..8d731f28 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterServerDemo.java @@ -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.demo.cluster; + +import java.util.Collections; + +import com.alibaba.csp.sentinel.cluster.server.ClusterTokenServer; +import com.alibaba.csp.sentinel.cluster.server.SentinelDefaultTokenServer; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; + +/** + * Cluster server demo (alone mode). + * + * Here we init the cluster server dynamic data sources in {@link com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc}. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterServerDemo { + + public static void main(String[] args) throws Exception { + // Not embedded mode by default (alone mode). + ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); + + // A sample for manually load config for cluster server. + // It's recommended to use dynamic data source to cluster manage config and rules. + // See the sample in DemoClusterServerInitFunc for detail. + ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig() + .setIdleSeconds(600) + .setPort(11111)); + ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton(DemoConstants.APP_NAME)); + + // Start the server. + tokenServer.start(); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java new file mode 100644 index 00000000..06ee4e03 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/DemoConstants.java @@ -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.demo.cluster; + +/** + * @author Eric Zhao + */ +public final class DemoConstants { + + public static final String APP_NAME = "appA"; + + public static final String FLOW_POSTFIX = "-flow-rules"; + public static final String PARAM_FLOW_POSTFIX = "-param-rules"; + + private DemoConstants() {} +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java new file mode 100644 index 00000000..d75804be --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterClientInitFunc.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.demo.cluster.init; + +import java.util.List; + +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig; +import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; +import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +/** + * @author Eric Zhao + */ +public class DemoClusterClientInitFunc implements InitFunc { + + @Override + public void init() throws Exception { + final String remoteAddress = "localhost"; + final String groupId = "SENTINEL_GROUP"; + final String flowDataId = DemoConstants.APP_NAME + DemoConstants.FLOW_POSTFIX; + final String paramDataId = DemoConstants.APP_NAME + DemoConstants.PARAM_FLOW_POSTFIX; + final String configDataId = DemoConstants.APP_NAME + "-cluster-client-config"; + + ReadableDataSource> ruleSource = new NacosDataSource<>(remoteAddress, groupId, + flowDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + FlowRuleManager.register2Property(ruleSource.getProperty()); + ReadableDataSource> paramRuleSource = new NacosDataSource<>(remoteAddress, groupId, + paramDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + ParamFlowRuleManager.register2Property(paramRuleSource.getProperty()); + + ReadableDataSource dataSource = new NacosDataSource<>(remoteAddress, groupId, + configDataId, source -> JSON.parseObject(source, new TypeReference() {})); + ClusterClientConfigManager.register2Property(dataSource.getProperty()); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java new file mode 100644 index 00000000..8fe5313a --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/java/com/alibaba/csp/sentinel/demo/cluster/init/DemoClusterServerInitFunc.java @@ -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.demo.cluster.init; + +import java.util.List; +import java.util.Set; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.server.config.ServerTransportConfig; +import com.alibaba.csp.sentinel.datasource.ReadableDataSource; +import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource; +import com.alibaba.csp.sentinel.demo.cluster.DemoConstants; +import com.alibaba.csp.sentinel.init.InitFunc; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +/** + * @author Eric Zhao + */ +public class DemoClusterServerInitFunc implements InitFunc { + + private final String remoteAddress = "localhost"; + private final String groupId = "SENTINEL_GROUP"; + private final String namespaceSetDataId = "cluster-server-namespace-set"; + private final String serverTransportDataId = "cluster-server-transport-config"; + + @Override + public void init() throws Exception { + // Register cluster flow rule property supplier which creates data source by namespace. + ClusterFlowRuleManager.setPropertySupplier(namespace -> { + ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, + namespace + DemoConstants.FLOW_POSTFIX, + source -> JSON.parseObject(source, new TypeReference>() {})); + return ds.getProperty(); + }); + // Register cluster parameter flow rule property supplier. + ClusterParamFlowRuleManager.setPropertySupplier(namespace -> { + ReadableDataSource> ds = new NacosDataSource<>(remoteAddress, groupId, + namespace + DemoConstants.PARAM_FLOW_POSTFIX, + source -> JSON.parseObject(source, new TypeReference>() {})); + return ds.getProperty(); + }); + + // Server namespace set (scope) data source. + ReadableDataSource> namespaceDs = new NacosDataSource<>(remoteAddress, groupId, + namespaceSetDataId, source -> JSON.parseObject(source, new TypeReference>() {})); + ClusterServerConfigManager.registerNamespaceSetProperty(namespaceDs.getProperty()); + // Server transport configuration data source. + ReadableDataSource transportConfigDs = new NacosDataSource<>(remoteAddress, + groupId, serverTransportDataId, + source -> JSON.parseObject(source, new TypeReference() {})); + ClusterServerConfigManager.registerServerTransportProperty(transportConfigDs.getProperty()); + } +} diff --git a/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc b/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc new file mode 100755 index 00000000..11b70ec5 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterClientInitFunc +com.alibaba.csp.sentinel.demo.cluster.init.DemoClusterServerInitFunc \ No newline at end of file