From d6237bee0ae1b83bf0813534bf939a882b6642f9 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Wed, 21 Nov 2018 17:55:27 +0800 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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