From d6237bee0ae1b83bf0813534bf939a882b6642f9 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 17:55:27 +0800 Subject: [PATCH 01/10] 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 a6534e5b13049092a467ef6cdc3bd4cefe64f5c2 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 17:59:40 +0800 Subject: [PATCH 02/10] 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 | 83 ++++--- .../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, 542 insertions(+), 154 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; - } - - FlowRule flowRule = (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; - } + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + if (!super.equals(o)) { return false; } - return true; + FlowRule rule = (FlowRule)o; + + 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 f539b5b8274b833355731119915ed8b6eb803f9a Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 19:02:21 +0800 Subject: [PATCH 03/10] 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 4f854c9eae3be87d3364f1400c19dada4e3986f3 Mon Sep 17 00:00:00 2001 From: cdfive <31885791+cdfive@users.noreply.github.com> Date: Mon, 26 Nov 2018 17:42:17 +0800 Subject: [PATCH 04/10] Fix padding issue of charts in monitoring page of Sentinel dashboard (#262) - Increase padding-left value of G2.chart in sentinel-dashboard --- .../src/main/webapp/resources/app/scripts/controllers/metric.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/metric.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/metric.js index ebb810f8..7986d2f6 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/metric.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/metric.js @@ -68,7 +68,7 @@ app.controller('MetricCtl', ['$scope', '$stateParams', 'MetricService', '$interv forceFit: true, width: 100, height: 250, - padding: [10, 30, 70, 30] + padding: [10, 30, 70, 50] }); var maxQps = 0; for (var i in metric.data) { From 0bc07f314e12365dbc9d32d37cd536ebce2e6cb2 Mon Sep 17 00:00:00 2001 From: cdfive <31885791+cdfive@users.noreply.github.com> Date: Thu, 29 Nov 2018 10:37:09 +0800 Subject: [PATCH 05/10] Optimize the click sensitivity of dashboard's sidebar menu (#268) - Use AngularJS binding model directly rather than jQuery DOM-based operation --- .../app/scripts/directives/sidebar/sidebar.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js index 45ed9fab..ce1a9030 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js @@ -33,16 +33,14 @@ angular.module('sentinelDashboardApp') // toggle side bar $scope.click = function ($event) { - let element = angular.element($event.target); let entry = angular.element($event.target).scope().entry; - entry.active = !entry.active; + entry.active = !entry.active;// toggle this clicked app bar - if (entry.active === false) { - element.parent().children('ul').hide(); - } else { - element.parent().parent().children('li').children('ul').hide(); - element.parent().children('ul').show(); - } + $scope.apps.forEach(function (item) {// collapse other app bars + if (item != entry) { + item.active = false; + } + }); }; /** From 223b4e47ee742252f0bffcc359f116e0bf2bed9a Mon Sep 17 00:00:00 2001 From: Carpenter Lee Date: Thu, 29 Nov 2018 16:02:27 +0800 Subject: [PATCH 06/10] Add verbose log when SimpleHttpCommandCenter chooses port fail --- .../transport/command/SimpleHttpCommandCenter.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/SimpleHttpCommandCenter.java b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/SimpleHttpCommandCenter.java index 193d844b..76a361aa 100755 --- a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/SimpleHttpCommandCenter.java +++ b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/command/SimpleHttpCommandCenter.java @@ -19,11 +19,11 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; @@ -53,7 +53,7 @@ public class SimpleHttpCommandCenter implements CommandCenter { private static final int DEFAULT_PORT = 8719; @SuppressWarnings("rawtypes") - private static final Map handlerMap = new HashMap(); + private static final Map handlerMap = new ConcurrentHashMap(); private ExecutorService executor = Executors.newSingleThreadExecutor( new NamedThreadFactory("sentinel-command-center-executor")); @@ -105,12 +105,14 @@ public class SimpleHttpCommandCenter implements CommandCenter { executor.submit(new ServerThread(serverSocket)); success = true; port = serverSocket.getLocalPort(); + } else { + CommandCenterLog.info("[CommandCenter] chooses port fail, http command center will not work"); } if (!success) { port = PORT_UNINITIALIZED; } - + TransportConfig.setRuntimePort(port); executor.shutdown(); } @@ -119,11 +121,11 @@ public class SimpleHttpCommandCenter implements CommandCenter { new Thread(serverInitTask).start(); } - + /** * Get a server socket from an available port from a base port.
    * Increasing on port number will occur when the port has already been used. - * + * * @param basePort base port to start * @return new socket with available port */ @@ -135,7 +137,7 @@ public class SimpleHttpCommandCenter implements CommandCenter { server.setReuseAddress(true); return server; } catch (IOException e) { - tryCount ++; + tryCount++; try { TimeUnit.MILLISECONDS.sleep(30); } catch (InterruptedException e1) { From eb29cc929f4a4470464380fc90b039e9c6d9cbc2 Mon Sep 17 00:00:00 2001 From: "nick.tan" Date: Mon, 3 Dec 2018 09:48:15 +0800 Subject: [PATCH 07/10] Add heartbeat client ip config to resolve ip problem in Docker env (#261) --- .../transport/config/TransportConfig.java | 17 +++++++++++++ .../transport/config/TransportConfigTest.java | 25 +++++++++++++++++++ .../heartbeat/HttpHeartbeatSender.java | 2 +- .../transport/heartbeat/HeartbeatMessage.java | 2 +- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/config/TransportConfigTest.java diff --git a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/config/TransportConfig.java b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/config/TransportConfig.java index 5f934a98..ab150175 100755 --- a/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/config/TransportConfig.java +++ b/sentinel-transport/sentinel-transport-common/src/main/java/com/alibaba/csp/sentinel/transport/config/TransportConfig.java @@ -16,6 +16,8 @@ package com.alibaba.csp.sentinel.transport.config; import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.util.HostNameUtil; +import com.alibaba.csp.sentinel.util.StringUtil; /** * @author leyou @@ -25,6 +27,7 @@ public class TransportConfig { public static final String CONSOLE_SERVER = "csp.sentinel.dashboard.server"; public static final String SERVER_PORT = "csp.sentinel.api.port"; public static final String HEARTBEAT_INTERVAL_MS = "csp.sentinel.heartbeat.interval.ms"; + public static final String HEARTBEAT_CLIENT_IP = "csp.sentinel.heartbeat.client.ip"; private static int runtimePort = -1; @@ -66,4 +69,18 @@ public class TransportConfig { public static void setRuntimePort(int port) { runtimePort = port; } + + /** + * Get heartbeat client local ip. + * If the client ip not configured,it will be the address of local host + * + * @return the local ip. + */ + public static String getHeartbeatClientIp() { + String ip = SentinelConfig.getConfig(HEARTBEAT_CLIENT_IP); + if (StringUtil.isBlank(ip)) { + ip = HostNameUtil.getIp(); + } + return ip; + } } diff --git a/sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/config/TransportConfigTest.java b/sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/config/TransportConfigTest.java new file mode 100644 index 00000000..d35c77fa --- /dev/null +++ b/sentinel-transport/sentinel-transport-common/src/test/java/com/alibaba/csp/sentinel/transport/config/TransportConfigTest.java @@ -0,0 +1,25 @@ +package com.alibaba.csp.sentinel.transport.config; + +import com.alibaba.csp.sentinel.config.SentinelConfig; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TransportConfigTest { + + @Test + public void getClientIp() { + //config heartbeat client ip + System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP, "10.10.10.10"); + String ip = TransportConfig.getHeartbeatClientIp(); + + assertNotNull(ip); + assertEquals(ip, "10.10.10.10"); + + //no heartbeat client ip + SentinelConfig.setConfig(TransportConfig.HEARTBEAT_CLIENT_IP, ""); + ip = TransportConfig.getHeartbeatClientIp(); + assertNotNull(ip); + + } +} \ No newline at end of file diff --git a/sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HttpHeartbeatSender.java b/sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HttpHeartbeatSender.java index f92e4744..090cd09b 100755 --- a/sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HttpHeartbeatSender.java +++ b/sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HttpHeartbeatSender.java @@ -85,7 +85,7 @@ public class HttpHeartbeatSender implements HeartbeatSender { .setParameter("v", Constants.SENTINEL_VERSION) .setParameter("version", String.valueOf(System.currentTimeMillis())) .setParameter("hostname", HostNameUtil.getHostName()) - .setParameter("ip", HostNameUtil.getIp()) + .setParameter("ip", TransportConfig.getHeartbeatClientIp()) .setParameter("port", TransportConfig.getPort()) .setParameter("pid", String.valueOf(PidUtil.getPid())); diff --git a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HeartbeatMessage.java b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HeartbeatMessage.java index 8292c7a3..5695632c 100755 --- a/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HeartbeatMessage.java +++ b/sentinel-transport/sentinel-transport-simple-http/src/main/java/com/alibaba/csp/sentinel/transport/heartbeat/HeartbeatMessage.java @@ -36,7 +36,7 @@ public class HeartbeatMessage { public HeartbeatMessage() { message.put("hostname", HostNameUtil.getHostName()); - message.put("ip", HostNameUtil.getIp()); + message.put("ip", TransportConfig.getHeartbeatClientIp()); message.put("app", AppNameUtil.getAppName()); message.put("port", String.valueOf(TransportConfig.getPort())); } From ad1d9f02707511da91ef73afdedca66fedb6710c Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Mon, 3 Dec 2018 15:13:14 +0800 Subject: [PATCH 08/10] Update README.md --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cbd11f8d..89729d08 100755 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ try { } ``` -So far the code modification is done. +So far the code modification is done. We also provide [annotation support module](https://github.com/alibaba/Sentinel/blob/master/sentinel-extension/sentinel-annotation-aspectj/README.md) to define resource easier. ### 3. Define Rules @@ -93,6 +93,8 @@ rules.add(rule); FlowRuleManager.loadRules(rules); ``` +For more information, please refer to [How To Use](https://github.com/alibaba/Sentinel/wiki/How-to-Use). + ### 4. Check the Result After running the demo for a while, you can see the following records in `~/logs/csp/${appName}-metrics.log`. @@ -144,3 +146,12 @@ These are only part of the companies using Sentinel, for reference only. If you ![Taiping Renshou](http://www.cntaiping.com/tplresource/cms/www/taiping/img/home_new/tp_logo_img.png) ![Shunfeng Technology](https://user-images.githubusercontent.com/9434884/48463502-2f48eb80-e817-11e8-984f-2f9b1b789e2d.png) ![Mandao](https://user-images.githubusercontent.com/9434884/48463559-6cad7900-e817-11e8-87e4-42952b074837.png) +![每日优鲜](https://home.missfresh.cn/statics/img/logo.png) +![二维火](https://user-images.githubusercontent.com/9434884/49358468-bc43de00-f70d-11e8-97fe-0bf05865f29f.png) +![文轩在线](http://static.winxuancdn.com/css/v2/images/logo.png) +![客如云](https://www.keruyun.com/static/krynew/images/logo.png) +![亲宝宝](https://stlib.qbb6.com/wclt/img/home_hd/version1/title_logo.png) +![杭州光云科技](https://www.raycloud.com/images/logo.png) +![金汇金融](https://res.jinhui365.com/r/images/logo2.png?v=1.527) +![Vivo](https://user-images.githubusercontent.com/9434884/49355264-c6f87600-f701-11e8-8109-054cf91df868.png) +![闪电购](http://cdn.52shangou.com/shandianbang/official-source/3.1.1/build/images/logo.png) From 6802f975285031ae48ba84d57dd4f8c30ec75ca1 Mon Sep 17 00:00:00 2001 From: Yuanqing Luo Date: Sun, 9 Dec 2018 23:08:03 +0800 Subject: [PATCH 09/10] Add HTTP-method level flow control support in Sentinel Web Servlet Filter (#282) - Add HTTP method level support for servlet filter with a init parameter `HTTP_METHOD_SPECIFY` as the switch. This is useful for REST APIs. - Add test cases and fix test bug by reset the cluster node in ClusterBuilderSlot --- .../adapter/servlet/CommonFilter.java | 24 +++- .../adapter/servlet/CommonFilterTest.java | 2 +- .../servletmethod/CommonFilterMethodTest.java | 133 ++++++++++++++++++ .../servletmethod/FilterMethodConfig.java | 25 ++++ .../servletmethod/TestApplication.java | 30 ++++ .../servletmethod/TestMethodController.java | 38 +++++ 6 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java create mode 100644 sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java create mode 100644 sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java create mode 100644 sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java diff --git a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java index 76b25381..f24bbe55 100755 --- a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java +++ b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java @@ -46,17 +46,23 @@ import com.alibaba.csp.sentinel.util.StringUtil; */ public class CommonFilter implements Filter { + private final static String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY"; + private final static String COLON = ":"; + private boolean httpMethodSpecify = false; + @Override public void init(FilterConfig filterConfig) { - + httpMethodSpecify = Boolean.parseBoolean(filterConfig.getInitParameter(HTTP_METHOD_SPECIFY)); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - HttpServletRequest sRequest = (HttpServletRequest)request; + throws IOException, ServletException { + HttpServletRequest sRequest = (HttpServletRequest) request; Entry entry = null; + Entry methodEntry = null; + try { String target = FilterUtil.filterTarget(sRequest); // Clean and unify the URL. @@ -73,9 +79,16 @@ public class CommonFilter implements Filter { ContextUtil.enter(target, origin); entry = SphU.entry(target, EntryType.IN); + + // Add method specification if necessary + if (httpMethodSpecify) { + methodEntry = SphU.entry(sRequest.getMethod().toUpperCase() + COLON + target, + EntryType.IN); + } + chain.doFilter(request, response); } catch (BlockException e) { - HttpServletResponse sResponse = (HttpServletResponse)response; + HttpServletResponse sResponse = (HttpServletResponse) response; // Return the block page, or redirect to another URL. WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e); } catch (IOException e2) { @@ -88,6 +101,9 @@ public class CommonFilter implements Filter { Tracer.trace(e4); throw e4; } finally { + if (methodEntry != null) { + methodEntry.exit(); + } if (entry != null) { entry.exit(); } diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java index 2602f17d..14e8b2a1 100644 --- a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilterTest.java @@ -171,6 +171,6 @@ public class CommonFilterTest { @After public void cleanUp() { FlowRuleManager.loadRules(null); - ClusterBuilderSlot.getClusterNodeMap().clear(); + ClusterBuilderSlot.resetClusterNodes(); } } diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java new file mode 100644 index 00000000..3df4c7d5 --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/CommonFilterMethodTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.servletmethod; + +import com.alibaba.csp.sentinel.adapter.servlet.config.WebServletConfig; +import com.alibaba.csp.sentinel.adapter.servlet.util.FilterUtil; +import com.alibaba.csp.sentinel.node.ClusterNode; +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.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.StringUtil; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Collections; + +import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * @author Roger Law + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +public class CommonFilterMethodTest { + + private static final String HELLO_STR = "Hello!"; + + private static final String HELLO_POST_STR = "Hello Post!"; + + private static final String GET = "GET"; + + private static final String POST = "POST"; + + private static final String COLON = ":"; + + @Autowired + private MockMvc mvc; + + private void configureRulesFor(String resource, int count) { + configureRulesFor(resource, count, "default"); + } + + private void configureRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } + + @Test + public void testCommonFilterMiscellaneous() throws Exception { + String url = "/hello"; + this.mvc.perform(get(url)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_STR)); + + ClusterNode cnGet = ClusterBuilderSlot.getClusterNode(GET + COLON + url); + assertNotNull(cnGet); + assertEquals(1, cnGet.passQps()); + + + ClusterNode cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url); + assertNull(cnPost); + + this.mvc.perform(post(url)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_POST_STR)); + + cnPost = ClusterBuilderSlot.getClusterNode(POST + COLON + url); + assertNotNull(cnPost); + assertEquals(1, cnPost.passQps()); + + testCommonBlockAndRedirectBlockPage(url, cnGet, cnPost); + + } + + private void testCommonBlockAndRedirectBlockPage(String url, ClusterNode cnGet, ClusterNode cnPost) throws Exception { + configureRulesFor(GET + ":" + url, 0); + // The request will be blocked and response is default block message. + this.mvc.perform(get(url).accept(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()) + .andExpect(content().string(FilterUtil.DEFAULT_BLOCK_MSG)); + assertEquals(1, cnGet.blockQps()); + + // Test for post pass + this.mvc.perform(post(url)) + .andExpect(status().isOk()) + .andExpect(content().string(HELLO_POST_STR)); + + assertEquals(2, cnPost.passQps()); + + + FlowRuleManager.loadRules(null); + WebServletConfig.setBlockPage(""); + } + + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.resetClusterNodes(); + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java new file mode 100644 index 00000000..d2c9d21f --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/FilterMethodConfig.java @@ -0,0 +1,25 @@ +package com.alibaba.csp.sentinel.adapter.servletmethod; + +import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author: Roger Law + **/ +@Configuration +public class FilterMethodConfig { + + @Bean + public FilterRegistrationBean sentinelFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new CommonFilter()); + registration.addUrlPatterns("/*"); + registration.addInitParameter("HTTP_METHOD_SPECIFY", "true"); + registration.setName("sentinelFilter"); + registration.setOrder(1); + + return registration; + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.java new file mode 100644 index 00000000..745d79a0 --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestApplication.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.adapter.servletmethod; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author Eric Zhao + */ +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.java new file mode 100644 index 00000000..04b08b40 --- /dev/null +++ b/sentinel-adapter/sentinel-web-servlet/src/test/java/com/alibaba/csp/sentinel/adapter/servletmethod/TestMethodController.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.adapter.servletmethod; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Roger Law + */ +@RestController +public class TestMethodController { + + @GetMapping("/hello") + public String apiHello() { + return "Hello!"; + } + + @PostMapping("/hello") + public String apiHelloPost() { + return "Hello Post!"; + } + +} From 512b27bba33f7bb49bb1c1248eb8129f65d5a121 Mon Sep 17 00:00:00 2001 From: leitao <1225214744@qq.com> Date: Tue, 11 Dec 2018 11:56:45 +0800 Subject: [PATCH 10/10] Fix javadoc typo in decreaseThreadNum() method of Node interface (#296) --- .../src/main/java/com/alibaba/csp/sentinel/node/Node.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java index f836ae0b..edf9be03 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/Node.java @@ -26,6 +26,7 @@ import com.alibaba.csp.sentinel.node.metric.MetricNode; * @author qinan.qn * @author leyou * @author Eric Zhao + * @author leitao */ public interface Node { @@ -147,7 +148,7 @@ public interface Node { void increaseThreadNum(); /** - * Increase current thread count. + * Decrease current thread count. */ void decreaseThreadNum();