From 6545a90d465a79b6e65777b23fe0c690036a9eaa Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Wed, 21 Nov 2018 17:55:27 +0800
Subject: [PATCH 01/20] Add basic interface and entity for Sentinel cluster
flow control
- Add a universal `TokenService` SPI interface for both local flow control and distributed flow control
- Add TokenResult entity to represents result of acquiring token
- Add `ClusterTokenClient` as the SPI interface for client of Sentinel cluster flow control
Signed-off-by: Eric Zhao
---
.../sentinel/cluster/ClusterTokenClient.java | 32 +++++++
.../sentinel/cluster/TokenClientProvider.java | 61 +++++++++++++
.../csp/sentinel/cluster/TokenResult.java | 86 +++++++++++++++++++
.../sentinel/cluster/TokenResultStatus.java | 60 +++++++++++++
.../cluster/TokenServerDescriptor.java | 72 ++++++++++++++++
.../csp/sentinel/cluster/TokenService.java | 47 ++++++++++
.../cluster/log/ClusterStatLogUtil.java | 59 +++++++++++++
.../slots/block/flow/FlowSlotTest.java | 16 ++--
8 files changed, 425 insertions(+), 8 deletions(-)
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
new file mode 100644
index 00000000..6a230c07
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+/**
+ * Token client interface for distributed flow control.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ClusterTokenClient extends TokenService {
+
+ /**
+ * Get descriptor of current token server.
+ *
+ * @return current token server
+ */
+ TokenServerDescriptor currentServer();
+}
\ No newline at end of file
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java
new file mode 100644
index 00000000..592046c9
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenClientProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ * Provider for a universal {@link ClusterTokenClient} instance.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class TokenClientProvider {
+
+ private static ClusterTokenClient client = null;
+
+ private static final ServiceLoader LOADER = ServiceLoader.load(ClusterTokenClient.class);
+
+ static {
+ // Not strictly thread-safe, but it's OK since it will be resolved only once.
+ resolveTokenClientInstance();
+ }
+
+ public static ClusterTokenClient getClient() {
+ return client;
+ }
+
+ private static void resolveTokenClientInstance() {
+ List clients = new ArrayList();
+ for (ClusterTokenClient client : LOADER) {
+ clients.add(client);
+ }
+
+ if (!clients.isEmpty()) {
+ // Get first.
+ client = clients.get(0);
+ RecordLog.info("[TokenClientProvider] Token client resolved: " + client.getClass().getCanonicalName());
+ } else {
+ RecordLog.warn("[TokenClientProvider] No existing token client, resolve failed");
+ }
+ }
+
+ private TokenClientProvider() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java
new file mode 100644
index 00000000..29fa064a
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+import java.util.Map;
+
+/**
+ * Result entity of acquiring cluster flow token.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class TokenResult {
+
+ private Integer status;
+
+ private int remaining;
+ private int waitInMs;
+
+ private Map attachments;
+
+ public TokenResult() {}
+
+ public TokenResult(Integer status) {
+ this.status = status;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public TokenResult setStatus(Integer status) {
+ this.status = status;
+ return this;
+ }
+
+ public int getRemaining() {
+ return remaining;
+ }
+
+ public TokenResult setRemaining(int remaining) {
+ this.remaining = remaining;
+ return this;
+ }
+
+ public int getWaitInMs() {
+ return waitInMs;
+ }
+
+ public TokenResult setWaitInMs(int waitInMs) {
+ this.waitInMs = waitInMs;
+ return this;
+ }
+
+ public Map getAttachments() {
+ return attachments;
+ }
+
+ public TokenResult setAttachments(Map attachments) {
+ this.attachments = attachments;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "TokenResult{" +
+ "status=" + status +
+ ", remaining=" + remaining +
+ ", waitInMs=" + waitInMs +
+ ", attachments=" + attachments +
+ '}';
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java
new file mode 100644
index 00000000..275d761e
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class TokenResultStatus {
+
+ /**
+ * Bad client request.
+ */
+ public static final int BAD_REQUEST = -4;
+ /**
+ * Server or client unexpected failure (due to transport or serialization failure).
+ */
+ public static final int FAIL = -1;
+
+ /**
+ * Token acquired.
+ */
+ public static final int OK = 0;
+
+ /**
+ * Token acquire failed (blocked).
+ */
+ public static final int BLOCKED = 1;
+ /**
+ * Should wait for next buckets.
+ */
+ public static final int SHOULD_WAIT = 2;
+ /**
+ * Token acquire failed (no rule exists).
+ */
+ public static final int NO_RULE_EXISTS = 3;
+ /**
+ * Token acquire failed (reference resource is not available).
+ */
+ public static final int NO_REF_RULE_EXISTS = 4;
+ /**
+ * Token acquire failed (strategy not available).
+ */
+ public static final int NOT_AVAILABLE = 5;
+
+ private TokenResultStatus() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
new file mode 100644
index 00000000..98739179
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+/**
+ * A simple descriptor for Sentinel token server.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class TokenServerDescriptor {
+
+ private String host;
+ private int port;
+ private String type;
+
+ public TokenServerDescriptor() {}
+
+ public TokenServerDescriptor(String host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public TokenServerDescriptor setHost(String host) {
+ this.host = host;
+ return this;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public TokenServerDescriptor setPort(int port) {
+ this.port = port;
+ return this;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public TokenServerDescriptor setType(String type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "TokenServerDescriptor{" +
+ "host='" + host + '\'' +
+ ", port=" + port +
+ ", type='" + type + '\'' +
+ '}';
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
new file mode 100644
index 00000000..d02c562f
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+import java.util.Collection;
+
+/**
+ * Service interface of flow control.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface TokenService {
+
+ /**
+ * Request tokens from remote token server.
+ *
+ * @param ruleId the unique rule ID
+ * @param acquireCount token count to acquire
+ * @param prioritized whether the request is prioritized
+ * @return result of the token request
+ */
+ TokenResult requestToken(Integer ruleId, int acquireCount, boolean prioritized);
+
+ /**
+ * Request tokens for a specific parameter from remote token server.
+ *
+ * @param ruleId the unique rule ID
+ * @param acquireCount token count to acquire
+ * @param params parameter list
+ * @return result of the token request
+ */
+ TokenResult requestParamToken(Integer ruleId, int acquireCount, Collection params);
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java
new file mode 100644
index 00000000..16f914c6
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.log;
+
+import java.io.File;
+
+import com.alibaba.csp.sentinel.eagleeye.EagleEye;
+import com.alibaba.csp.sentinel.eagleeye.StatLogger;
+import com.alibaba.csp.sentinel.log.LogBase;
+
+/**
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterStatLogUtil {
+
+ private static final String FILE_NAME = "sentinel-cluster.log";
+
+ private static StatLogger statLogger;
+
+ static {
+ String path = LogBase.getLogBaseDir() + FILE_NAME;
+
+ statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record")
+ .intervalSeconds(1)
+ .entryDelimiter('|')
+ .keyDelimiter(',')
+ .valueDelimiter(',')
+ .maxEntryCount(5000)
+ .configLogFilePath(path)
+ .maxFileSizeMB(300)
+ .maxBackupIndex(3)
+ .buildSingleton();
+ }
+
+ public static void log(String msg) {
+ statLogger.stat(msg).count();
+ }
+
+ public static void log(String msg, int count) {
+ statLogger.stat(msg).count(count);
+ }
+
+ private ClusterStatLogUtil() {}
+}
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java
index 552f8407..ee5de0a8 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java
@@ -54,7 +54,7 @@ public class FlowSlotTest {
Context context = mock(Context.class);
DefaultNode node = mock(DefaultNode.class);
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class),
- any(DefaultNode.class), anyInt());
+ any(DefaultNode.class), anyInt(), anyBoolean());
String resA = "resAK";
String resB = "resBK";
@@ -63,13 +63,13 @@ public class FlowSlotTest {
// Here we only load rules for resA.
FlowRuleManager.loadRules(Collections.singletonList(rule1));
- when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt()))
+ when(flowSlot.canPassCheck(eq(rule1), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean()))
.thenReturn(true);
- when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt()))
+ when(flowSlot.canPassCheck(eq(rule2), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean()))
.thenReturn(false);
- flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1);
- flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1);
+ flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false);
+ flowSlot.checkFlow(new StringResourceWrapper(resB, EntryType.IN), context, node, 1, false);
}
@Test(expected = FlowException.class)
@@ -78,15 +78,15 @@ public class FlowSlotTest {
Context context = mock(Context.class);
DefaultNode node = mock(DefaultNode.class);
doCallRealMethod().when(flowSlot).checkFlow(any(ResourceWrapper.class), any(Context.class),
- any(DefaultNode.class), anyInt());
+ any(DefaultNode.class), anyInt(), anyBoolean());
String resA = "resAK";
FlowRule rule = new FlowRule(resA).setCount(10);
FlowRuleManager.loadRules(Collections.singletonList(rule));
- when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt()))
+ when(flowSlot.canPassCheck(any(FlowRule.class), any(Context.class), any(DefaultNode.class), anyInt(), anyBoolean()))
.thenReturn(false);
- flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1);
+ flowSlot.checkFlow(new StringResourceWrapper(resA, EntryType.IN), context, node, 1, false);
}
}
\ No newline at end of file
From b215e8784e2fd3f7f197530c5198e693c9b33154 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Wed, 21 Nov 2018 17:59:40 +0800
Subject: [PATCH 02/20] Update flow rule for cluster mode and extract util
class
- Add new field `clusterMode` and `clusterConfig` for cluster mode
- Add a `ClusterFlowConfig` class for specific items for cluster flow control
- Update FlowRuleChecker to support cluster mode
- Extract valid rule checking and rule map generating logic to FlowRuleUtil
Signed-off-by: Eric Zhao
---
.../slots/block/ClusterRuleConstant.java | 31 +++
.../slots/block/flow/ClusterFlowConfig.java | 154 ++++++++++++
.../sentinel/slots/block/flow/FlowRule.java | 81 ++++---
.../slots/block/flow/FlowRuleChecker.java | 53 +++-
.../slots/block/flow/FlowRuleComparator.java | 13 +
.../slots/block/flow/FlowRuleManager.java | 123 ++--------
.../slots/block/flow/FlowRuleUtil.java | 227 ++++++++++++++++++
.../sentinel/slots/block/flow/FlowSlot.java | 12 +-
8 files changed, 541 insertions(+), 153 deletions(-)
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java
new file mode 100644
index 00000000..78cacf34
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.block;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterRuleConstant {
+
+ public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0;
+ public static final int FLOW_CLUSTER_STRATEGY_REF = 1;
+
+ public static final int FLOW_THRESHOLD_AVG_LOCAL = 0;
+ public static final int FLOW_THRESHOLD_GLOBAL = 1;
+
+ private ClusterRuleConstant() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
new file mode 100644
index 00000000..506701e2
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+
+/**
+ * Flow rule config in cluster mode.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterFlowConfig {
+
+ /**
+ * Global unique ID.
+ */
+ private Integer flowId;
+
+ /**
+ * Threshold type (average by local value or global value).
+ */
+ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
+ private boolean fallbackToLocalWhenFail;
+
+ /**
+ * 0: normal; 1: using reference (borrow from reference).
+ */
+ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
+
+ private Integer refFlowId;
+ private int refSampleCount = 10;
+ private double refRatio = 1d;
+
+ public Integer getFlowId() {
+ return flowId;
+ }
+
+ public ClusterFlowConfig setFlowId(Integer flowId) {
+ this.flowId = flowId;
+ return this;
+ }
+
+ public int getThresholdType() {
+ return thresholdType;
+ }
+
+ public ClusterFlowConfig setThresholdType(int thresholdType) {
+ this.thresholdType = thresholdType;
+ return this;
+ }
+
+ public int getStrategy() {
+ return strategy;
+ }
+
+ public ClusterFlowConfig setStrategy(int strategy) {
+ this.strategy = strategy;
+ return this;
+ }
+
+ public Integer getRefFlowId() {
+ return refFlowId;
+ }
+
+ public ClusterFlowConfig setRefFlowId(Integer refFlowId) {
+ this.refFlowId = refFlowId;
+ return this;
+ }
+
+ public int getRefSampleCount() {
+ return refSampleCount;
+ }
+
+ public ClusterFlowConfig setRefSampleCount(int refSampleCount) {
+ this.refSampleCount = refSampleCount;
+ return this;
+ }
+
+ public double getRefRatio() {
+ return refRatio;
+ }
+
+ public ClusterFlowConfig setRefRatio(double refRatio) {
+ this.refRatio = refRatio;
+ return this;
+ }
+
+ public boolean isFallbackToLocalWhenFail() {
+ return fallbackToLocalWhenFail;
+ }
+
+ public ClusterFlowConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) {
+ this.fallbackToLocalWhenFail = fallbackToLocalWhenFail;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+
+ ClusterFlowConfig that = (ClusterFlowConfig)o;
+
+ if (thresholdType != that.thresholdType) { return false; }
+ if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; }
+ if (strategy != that.strategy) { return false; }
+ if (refSampleCount != that.refSampleCount) { return false; }
+ if (Double.compare(that.refRatio, refRatio) != 0) { return false; }
+ if (flowId != null ? !flowId.equals(that.flowId) : that.flowId != null) { return false; }
+ return refFlowId != null ? refFlowId.equals(that.refFlowId) : that.refFlowId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = flowId != null ? flowId.hashCode() : 0;
+ result = 31 * result + thresholdType;
+ result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0);
+ result = 31 * result + strategy;
+ result = 31 * result + (refFlowId != null ? refFlowId.hashCode() : 0);
+ result = 31 * result + refSampleCount;
+ temp = Double.doubleToLongBits(refRatio);
+ result = 31 * result + (int)(temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ClusterFlowConfig{" +
+ "flowId=" + flowId +
+ ", thresholdType=" + thresholdType +
+ ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail +
+ ", strategy=" + strategy +
+ ", refFlowId=" + refFlowId +
+ ", refSampleCount=" + refSampleCount +
+ ", refRatio=" + refRatio +
+ '}';
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java
index dc42a89e..e7413ea2 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java
@@ -85,6 +85,12 @@ public class FlowRule extends AbstractRule {
*/
private int maxQueueingTimeMs = 500;
+ private boolean clusterMode;
+ /**
+ * Flow rule config for cluster mode.
+ */
+ private ClusterFlowConfig clusterConfig;
+
/**
* The traffic shaping (throttling) controller.
*/
@@ -162,6 +168,24 @@ public class FlowRule extends AbstractRule {
return this;
}
+ public boolean isClusterMode() {
+ return clusterMode;
+ }
+
+ public FlowRule setClusterMode(boolean clusterMode) {
+ this.clusterMode = clusterMode;
+ return this;
+ }
+
+ public ClusterFlowConfig getClusterConfig() {
+ return clusterConfig;
+ }
+
+ public FlowRule setClusterConfig(ClusterFlowConfig clusterConfig) {
+ this.clusterConfig = clusterConfig;
+ return this;
+ }
+
@Override
public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) {
return true;
@@ -169,43 +193,21 @@ public class FlowRule extends AbstractRule {
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof FlowRule)) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+ if (!super.equals(o)) { return false; }
- FlowRule flowRule = (FlowRule)o;
+ FlowRule rule = (FlowRule)o;
- if (grade != flowRule.grade) {
- return false;
- }
- if (Double.compare(flowRule.count, count) != 0) {
- return false;
- }
- if (strategy != flowRule.strategy) {
- return false;
- }
- if (refResource != null ? !refResource.equals(flowRule.refResource) : flowRule.refResource != null) {
- return false;
- }
- if (this.controlBehavior != flowRule.controlBehavior) {
- return false;
- }
-
- if (warmUpPeriodSec != flowRule.warmUpPeriodSec) {
- return false;
- }
-
- if (maxQueueingTimeMs != flowRule.maxQueueingTimeMs) {
- return false;
- }
-
- return true;
+ if (grade != rule.grade) { return false; }
+ if (Double.compare(rule.count, count) != 0) { return false; }
+ if (strategy != rule.strategy) { return false; }
+ if (controlBehavior != rule.controlBehavior) { return false; }
+ if (warmUpPeriodSec != rule.warmUpPeriodSec) { return false; }
+ if (maxQueueingTimeMs != rule.maxQueueingTimeMs) { return false; }
+ if (clusterMode != rule.clusterMode) { return false; }
+ if (refResource != null ? !refResource.equals(rule.refResource) : rule.refResource != null) { return false; }
+ return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null;
}
@Override
@@ -217,10 +219,11 @@ public class FlowRule extends AbstractRule {
result = 31 * result + (int)(temp ^ (temp >>> 32));
result = 31 * result + strategy;
result = 31 * result + (refResource != null ? refResource.hashCode() : 0);
- result = 31 * result + (int)(temp ^ (temp >>> 32));
- result = 31 * result + warmUpPeriodSec;
result = 31 * result + controlBehavior;
+ result = 31 * result + warmUpPeriodSec;
result = 31 * result + maxQueueingTimeMs;
+ result = 31 * result + (clusterMode ? 1 : 0);
+ result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0);
return result;
}
@@ -236,7 +239,9 @@ public class FlowRule extends AbstractRule {
", controlBehavior=" + controlBehavior +
", warmUpPeriodSec=" + warmUpPeriodSec +
", maxQueueingTimeMs=" + maxQueueingTimeMs +
+ ", clusterMode=" + clusterMode +
+ ", clusterConfig=" + clusterConfig +
", controller=" + controller +
- "}";
+ '}';
}
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java
index 197a3bbf..7518d7c1 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java
@@ -15,7 +15,12 @@
*/
package com.alibaba.csp.sentinel.slots.block.flow;
+import com.alibaba.csp.sentinel.cluster.ClusterTokenClient;
+import com.alibaba.csp.sentinel.cluster.TokenClientProvider;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.context.Context;
+import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
@@ -30,11 +35,25 @@ import com.alibaba.csp.sentinel.util.StringUtil;
final class FlowRuleChecker {
static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount) {
+ return passCheck(rule, context, node, acquireCount, false);
+ }
+
+ static boolean passCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount,
+ boolean prioritized) {
String limitApp = rule.getLimitApp();
if (limitApp == null) {
return true;
}
+ if (rule.isClusterMode()) {
+ return passClusterCheck(rule, context, node, acquireCount, prioritized);
+ }
+
+ return passLocalCheck(rule, context, node, acquireCount, prioritized);
+ }
+
+ private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
+ boolean prioritized) {
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
return true;
@@ -43,6 +62,38 @@ final class FlowRuleChecker {
return rule.getRater().canPass(selectedNode, acquireCount);
}
+ static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
+ boolean prioritized) {
+ try {
+ ClusterTokenClient client = TokenClientProvider.getClient();
+ if (client != null) {
+ TokenResult result = client.requestToken(rule.getClusterConfig().getFlowId(), acquireCount,
+ prioritized);
+ switch (result.getStatus()) {
+ case TokenResultStatus.OK:
+ return true;
+ case TokenResultStatus.SHOULD_WAIT:
+ // Wait for next tick.
+ Thread.sleep(result.getWaitInMs());
+ return true;
+ case TokenResultStatus.NO_RULE_EXISTS:
+ case TokenResultStatus.BAD_REQUEST:
+ case TokenResultStatus.FAIL:
+ return passLocalCheck(rule, context, node, acquireCount, prioritized);
+ case TokenResultStatus.BLOCKED:
+ default:
+ return false;
+ }
+ }
+ // If client is absent, then fallback to local mode.
+ } catch (Throwable ex) {
+ RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);
+ }
+ // TODO: choose whether fallback to local or inactivate the rule.
+ // Downgrade to local flow control when token client or server for this rule is not available.
+ return passLocalCheck(rule, context, node, acquireCount, prioritized);
+ }
+
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
String refResource = rule.getRefResource();
int strategy = rule.getStrategy();
@@ -103,4 +154,4 @@ final class FlowRuleChecker {
}
private FlowRuleChecker() {}
-}
+}
\ No newline at end of file
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java
index 688bed96..ed1b22c6 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java
@@ -19,10 +19,23 @@ import java.util.Comparator;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+/**
+ * Comparator for flow rules.
+ *
+ * @author jialiang.linjl
+ */
public class FlowRuleComparator implements Comparator {
@Override
public int compare(FlowRule o1, FlowRule o2) {
+ // Clustered mode will be on the top.
+ if (o1.isClusterMode() && !o2.isClusterMode()) {
+ return 1;
+ }
+
+ if (!o1.isClusterMode() && o2.isClusterMode()) {
+ return -1;
+ }
if (o1.getLimitApp() == null) {
return 0;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java
index 230b4679..9dd23026 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java
@@ -16,8 +16,6 @@
package com.alibaba.csp.sentinel.slots.block.flow;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -32,16 +30,10 @@ import com.alibaba.csp.sentinel.node.metric.MetricTimerListener;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;
-import com.alibaba.csp.sentinel.slots.block.RuleConstant;
-import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController;
-import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController;
-import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController;
-import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController;
/**
*
- * One resources can have multiple rules. And these rules take effects in the
- * following order:
+ * One resources can have multiple rules. And these rules take effects in the following order:
*
* requests from specified caller
* no specified caller
@@ -49,18 +41,21 @@ import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterCon
*
*
* @author jialiang.linjl
+ * @author Eric Zhao
*/
public class FlowRuleManager {
private static final Map> flowRules = new ConcurrentHashMap>();
- private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
- new NamedThreadFactory("sentinel-metrics-record-task", true));
- private final static FlowPropertyListener listener = new FlowPropertyListener();
+
+ private static final FlowPropertyListener LISTENER = new FlowPropertyListener();
private static SentinelProperty> currentProperty = new DynamicSentinelProperty>();
+ private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
+ new NamedThreadFactory("sentinel-metrics-record-task", true));
+
static {
- currentProperty.addListener(listener);
- scheduler.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS);
+ currentProperty.addListener(LISTENER);
+ SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS);
}
/**
@@ -70,10 +65,10 @@ public class FlowRuleManager {
* @param property the property to listen.
*/
public static void register2Property(SentinelProperty> property) {
- synchronized (listener) {
+ synchronized (LISTENER) {
RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager");
- currentProperty.removeListener(listener);
- property.addListener(listener);
+ currentProperty.removeListener(LISTENER);
+ property.addListener(LISTENER);
currentProperty = property;
}
}
@@ -100,65 +95,7 @@ public class FlowRuleManager {
currentProperty.updateValue(rules);
}
- private static Map> loadFlowConf(List list) {
- Map> newRuleMap = new ConcurrentHashMap>();
-
- if (list == null || list.isEmpty()) {
- return newRuleMap;
- }
-
- for (FlowRule rule : list) {
- if (!isValidRule(rule)) {
- RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
- continue;
- }
- if (StringUtil.isBlank(rule.getLimitApp())) {
- rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
- }
-
- TrafficShapingController rater = new DefaultController(rule.getCount(), rule.getGrade());
- if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS
- && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP
- && rule.getWarmUpPeriodSec() > 0) {
- rater = new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), ColdFactorProperty.coldFactor);
-
- } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS
- && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
- && rule.getMaxQueueingTimeMs() > 0) {
- rater = new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
- } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS
- && rule.getControlBehavior() == RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER
- && rule.getMaxQueueingTimeMs() > 0 && rule.getWarmUpPeriodSec() > 0) {
- rater = new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
- rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
- }
-
- rule.setRater(rater);
-
- String identity = rule.getResource();
- List ruleM = newRuleMap.get(identity);
-
- if (ruleM == null) {
- ruleM = new ArrayList();
- newRuleMap.put(identity, ruleM);
- }
-
- ruleM.add(rule);
-
- }
-
- if (!newRuleMap.isEmpty()) {
- Comparator comparator = new FlowRuleComparator();
- // Sort the rules.
- for (List rules : newRuleMap.values()) {
- Collections.sort(rules, comparator);
- }
- }
-
- return newRuleMap;
- }
-
- static Map> getFlowRules() {
+ static Map> getFlowRuleMap() {
return flowRules;
}
@@ -188,7 +125,7 @@ public class FlowRuleManager {
@Override
public void configUpdate(List value) {
- Map> rules = loadFlowConf(value);
+ Map> rules = FlowRuleUtil.buildFlowRuleMap(value);
if (rules != null) {
flowRules.clear();
flowRules.putAll(rules);
@@ -198,43 +135,13 @@ public class FlowRuleManager {
@Override
public void configLoad(List conf) {
- Map> rules = loadFlowConf(conf);
+ Map> rules = FlowRuleUtil.buildFlowRuleMap(conf);
if (rules != null) {
flowRules.clear();
flowRules.putAll(rules);
}
RecordLog.info("[FlowRuleManager] Flow rules loaded: " + flowRules);
}
-
}
- public static boolean isValidRule(FlowRule rule) {
- boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
- && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0;
- if (!baseValid) {
- return false;
- }
- // Check strategy and control (shaping) behavior.
- return checkStrategyField(rule) && checkControlBehaviorField(rule);
- }
-
- private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) {
- if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) {
- return StringUtil.isNotBlank(rule.getRefResource());
- }
- return true;
- }
-
- private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) {
- switch (rule.getControlBehavior()) {
- case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
- return rule.getWarmUpPeriodSec() > 0;
- case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
- return rule.getMaxQueueingTimeMs() > 0;
- case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
- return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0;
- default:
- return true;
- }
- }
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
new file mode 100644
index 00000000..aa5a58c6
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.block.flow;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController;
+import com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController;
+import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController;
+import com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import com.alibaba.csp.sentinel.util.function.Function;
+import com.alibaba.csp.sentinel.util.function.Predicate;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class FlowRuleUtil {
+
+ /**
+ * Build the flow rule map from raw list of flow rules, grouping by resource name.
+ *
+ * @param list raw list of flow rules
+ * @return constructed new flow rule map; empty map if list is null or empty, or no valid rules
+ */
+ public static Map> buildFlowRuleMap(List list) {
+ return buildFlowRuleMap(list, null);
+ }
+
+ /**
+ * Build the flow rule map from raw list of flow rules, grouping by resource name.
+ *
+ * @param list raw list of flow rules
+ * @param filter rule filter
+ * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules
+ */
+ public static Map> buildFlowRuleMap(List list, Predicate filter) {
+ return buildFlowRuleMap(list, filter, true);
+ }
+
+ /**
+ * Build the flow rule map from raw list of flow rules, grouping by resource name.
+ *
+ * @param list raw list of flow rules
+ * @param filter rule filter
+ * @param shouldSort whether the rules should be sorted
+ * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules
+ */
+ public static Map> buildFlowRuleMap(List list, Predicate filter,
+ boolean shouldSort) {
+ return buildFlowRuleMap(list, extractResource, filter, shouldSort);
+ }
+
+ /**
+ * Build the flow rule map from raw list of flow rules, grouping by provided group function.
+ *
+ * @param list raw list of flow rules
+ * @param groupFunction grouping function of the map (by key)
+ * @param filter rule filter
+ * @param shouldSort whether the rules should be sorted
+ * @param type of key
+ * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules
+ */
+ public static Map> buildFlowRuleMap(List list, Function groupFunction,
+ Predicate filter, boolean shouldSort) {
+ Map> newRuleMap = new ConcurrentHashMap>();
+ if (list == null || list.isEmpty()) {
+ return newRuleMap;
+ }
+
+ for (FlowRule rule : list) {
+ if (!isValidRule(rule)) {
+ RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
+ continue;
+ }
+ if (filter != null && !filter.test(rule)) {
+ continue;
+ }
+ if (StringUtil.isBlank(rule.getLimitApp())) {
+ rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ TrafficShapingController rater = generateRater(rule);
+ rule.setRater(rater);
+
+ K key = groupFunction.apply(rule);
+ if (key == null) {
+ continue;
+ }
+ List flowRules = newRuleMap.get(key);
+
+ if (flowRules == null) {
+ flowRules = new ArrayList();
+ newRuleMap.put(key, flowRules);
+ }
+
+ flowRules.add(rule);
+ }
+
+ if (shouldSort && !newRuleMap.isEmpty()) {
+ Comparator comparator = new FlowRuleComparator();
+ // Sort the rules.
+ for (List rules : newRuleMap.values()) {
+ Collections.sort(rules, comparator);
+ }
+ }
+
+ return newRuleMap;
+ }
+
+ private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) {
+ if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
+ switch (rule.getControlBehavior()) {
+ case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
+ return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(),
+ ColdFactorProperty.coldFactor);
+ case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
+ return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount());
+ case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
+ return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(),
+ rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor);
+ case RuleConstant.CONTROL_BEHAVIOR_DEFAULT:
+ default:
+ // Default mode or unknown mode: default traffic shaping controller (fast-reject).
+ }
+ }
+ return new DefaultController(rule.getCount(), rule.getGrade());
+ }
+
+ /**
+ * Check whether provided ID can be a valid cluster flow ID.
+ *
+ * @param id flow ID to check
+ * @return true if valid, otherwise false
+ */
+ public static boolean validClusterRuleId(Integer id) {
+ return id != null && id > 0;
+ }
+
+ /**
+ * Check whether provided flow rule is valid.
+ *
+ * @param rule flow rule to check
+ * @return true if valid, otherwise false
+ */
+ public static boolean isValidRule(FlowRule rule) {
+ boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
+ && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0;
+ if (!baseValid) {
+ return false;
+ }
+ // Check strategy and control (shaping) behavior.
+ return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule);
+ }
+
+ private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) {
+ if (!rule.isClusterMode()) {
+ return true;
+ }
+ ClusterFlowConfig clusterConfig = rule.getClusterConfig();
+ if (clusterConfig == null) {
+ return false;
+ }
+ if (!validClusterRuleId(clusterConfig.getFlowId())) {
+ return false;
+ }
+ switch (rule.getStrategy()) {
+ case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL:
+ return true;
+ case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF:
+ return validClusterRuleId(clusterConfig.getRefFlowId());
+ default:
+ return true;
+ }
+ }
+
+ private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) {
+ if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) {
+ return StringUtil.isNotBlank(rule.getRefResource());
+ }
+ return true;
+ }
+
+ private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) {
+ switch (rule.getControlBehavior()) {
+ case RuleConstant.CONTROL_BEHAVIOR_WARM_UP:
+ return rule.getWarmUpPeriodSec() > 0;
+ case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER:
+ return rule.getMaxQueueingTimeMs() > 0;
+ case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER:
+ return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0;
+ default:
+ return true;
+ }
+ }
+
+ private static final Function extractResource = new Function() {
+ @Override
+ public String apply(FlowRule rule) {
+ return rule.getResource();
+ }
+ };
+
+ private FlowRuleUtil() {}
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java
index 4bd8ca5d..3f392db1 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java
@@ -138,27 +138,27 @@ public class FlowSlot extends AbstractLinkedProcessorSlot {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
- checkFlow(resourceWrapper, context, node, count);
+ checkFlow(resourceWrapper, context, node, count, prioritized);
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
- void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count) throws BlockException {
+ void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
// Flow rule map cannot be null.
- Map> flowRules = FlowRuleManager.getFlowRules();
+ Map> flowRules = FlowRuleManager.getFlowRuleMap();
List rules = flowRules.get(resource.getName());
if (rules != null) {
for (FlowRule rule : rules) {
- if (!canPassCheck(rule, context, node, count)) {
+ if (!canPassCheck(rule, context, node, count, prioritized)) {
throw new FlowException(rule.getLimitApp());
}
}
}
}
- boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count) {
- return FlowRuleChecker.passCheck(rule, context, node, count);
+ boolean canPassCheck(FlowRule rule, Context context, DefaultNode node, int count, boolean prioritized) {
+ return FlowRuleChecker.passCheck(rule, context, node, count, prioritized);
}
@Override
From 1043648fbc7afe97063ea52adea95fc2607f45d8 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Wed, 21 Nov 2018 19:02:21 +0800
Subject: [PATCH 03/20] Update parameter flow rule to adapt to cluster mode and
extract rule util class
- Update ParamFlowRule to support cluster mode
- Add `ParamFlowClusterConfig` to provide cluster mode items for the rule
- Update ParamFlowChecker to support cluster flow mode
- Extract ParamFlowRuleUtil class
- Change type of `flowId` from Integer to Long
Signed-off-by: Eric Zhao
---
.../csp/sentinel/cluster/TokenService.java | 4 +-
.../slots/block/flow/ClusterFlowConfig.java | 12 +-
.../slots/block/flow/FlowRuleUtil.java | 2 +-
.../block/flow/param/ParamFlowChecker.java | 79 +++++++++++-
.../flow/param/ParamFlowClusterConfig.java | 94 +++++++++++++++
.../slots/block/flow/param/ParamFlowRule.java | 35 +++++-
.../flow/param/ParamFlowRuleManager.java | 68 +----------
.../block/flow/param/ParamFlowRuleUtil.java | 114 ++++++++++++++++++
.../flow/param/ParamFlowRuleManagerTest.java | 82 -------------
.../flow/param/ParamFlowRuleUtilTest.java | 95 +++++++++++++++
10 files changed, 419 insertions(+), 166 deletions(-)
create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java
create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java
create mode 100644 sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
index d02c562f..f1d06292 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java
@@ -33,7 +33,7 @@ public interface TokenService {
* @param prioritized whether the request is prioritized
* @return result of the token request
*/
- TokenResult requestToken(Integer ruleId, int acquireCount, boolean prioritized);
+ TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized);
/**
* Request tokens for a specific parameter from remote token server.
@@ -43,5 +43,5 @@ public interface TokenService {
* @param params parameter list
* @return result of the token request
*/
- TokenResult requestParamToken(Integer ruleId, int acquireCount, Collection params);
+ TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params);
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
index 506701e2..b12a0743 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
@@ -28,7 +28,7 @@ public class ClusterFlowConfig {
/**
* Global unique ID.
*/
- private Integer flowId;
+ private Long flowId;
/**
* Threshold type (average by local value or global value).
@@ -41,15 +41,15 @@ public class ClusterFlowConfig {
*/
private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
- private Integer refFlowId;
+ private Long refFlowId;
private int refSampleCount = 10;
private double refRatio = 1d;
- public Integer getFlowId() {
+ public Long getFlowId() {
return flowId;
}
- public ClusterFlowConfig setFlowId(Integer flowId) {
+ public ClusterFlowConfig setFlowId(Long flowId) {
this.flowId = flowId;
return this;
}
@@ -72,11 +72,11 @@ public class ClusterFlowConfig {
return this;
}
- public Integer getRefFlowId() {
+ public Long getRefFlowId() {
return refFlowId;
}
- public ClusterFlowConfig setRefFlowId(Integer refFlowId) {
+ public ClusterFlowConfig setRefFlowId(Long refFlowId) {
this.refFlowId = refFlowId;
return this;
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
index aa5a58c6..bea269f1 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
@@ -155,7 +155,7 @@ public final class FlowRuleUtil {
* @param id flow ID to check
* @return true if valid, otherwise false
*/
- public static boolean validClusterRuleId(Integer id) {
+ public static boolean validClusterRuleId(Long id) {
return id != null && id > 0;
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java
index f98ef04d..2488643f 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowChecker.java
@@ -16,14 +16,23 @@
package com.alibaba.csp.sentinel.slots.block.flow.param;
import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Set;
+import com.alibaba.csp.sentinel.cluster.ClusterTokenClient;
+import com.alibaba.csp.sentinel.cluster.TokenClientProvider;
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
/**
+ * Rule checker for parameter flow control.
+ *
* @author Eric Zhao
* @since 0.2.0
*/
@@ -40,17 +49,72 @@ final class ParamFlowChecker {
return true;
}
+ // Get parameter value. If value is null, then pass.
Object value = args[paramIdx];
+ if (value == null) {
+ return true;
+ }
+
+ if (rule.isClusterMode()) {
+ return passClusterCheck(resourceWrapper, rule, count, value);
+ }
return passLocalCheck(resourceWrapper, rule, count, value);
}
- private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) {
- // Should not be null.
- return ParamFlowSlot.getParamMetric(resourceWrapper);
+ @SuppressWarnings("unchecked")
+ private static Collection toCollection(Object value) {
+ if (value instanceof Collection) {
+ return (Collection)value;
+ } else if (value.getClass().isArray()) {
+ List params = new ArrayList();
+ int length = Array.getLength(value);
+ for (int i = 0; i < length; i++) {
+ Object param = Array.get(value, i);
+ params.add(param);
+ }
+ return params;
+ } else {
+ return Collections.singletonList(value);
+ }
}
- private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) {
+ private static boolean passClusterCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
+ Object value) {
+ try {
+ ClusterTokenClient client = TokenClientProvider.getClient();
+ if (client == null) {
+ return true;
+ }
+ Collection params = toCollection(value);
+
+ TokenResult result = client.requestParamToken(rule.getClusterConfig().getFlowId(), count, params);
+ switch (result.getStatus()) {
+ case TokenResultStatus.OK:
+ return true;
+ case TokenResultStatus.BLOCKED:
+ return false;
+ default:
+ return fallbackToLocalOrPass(resourceWrapper, rule, count, params);
+ }
+ } catch (Throwable ex) {
+ RecordLog.warn("[ParamFlowChecker] Request cluster token for parameter unexpected failed", ex);
+ return fallbackToLocalOrPass(resourceWrapper, rule, count, value);
+ }
+ }
+
+ private static boolean fallbackToLocalOrPass(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
+ Object value) {
+ if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {
+ return passLocalCheck(resourceWrapper, rule, count, value);
+ } else {
+ // The rule won't be activated, just pass.
+ return true;
+ }
+ }
+
+ private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count,
+ Object value) {
try {
if (Collection.class.isAssignableFrom(value.getClass())) {
for (Object param : ((Collection)value)) {
@@ -70,7 +134,7 @@ final class ParamFlowChecker {
return passSingleValueCheck(resourceWrapper, rule, count, value);
}
} catch (Throwable e) {
- RecordLog.info("[ParamFlowChecker] Unexpected error", e);
+ RecordLog.warn("[ParamFlowChecker] Unexpected error", e);
}
return true;
@@ -96,5 +160,10 @@ final class ParamFlowChecker {
return true;
}
+ private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) {
+ // Should not be null.
+ return ParamFlowSlot.getParamMetric(resourceWrapper);
+ }
+
private ParamFlowChecker() {}
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java
new file mode 100644
index 00000000..2a532df6
--- /dev/null
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowClusterConfig.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.block.flow.param;
+
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+
+/**
+ * Parameter flow rule config in cluster mode.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ParamFlowClusterConfig {
+
+ /**
+ * Global unique ID.
+ */
+ private Long flowId;
+
+ /**
+ * Threshold type (average by local value or global value).
+ */
+ private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL;
+ private boolean fallbackToLocalWhenFail = false;
+
+ public Long getFlowId() {
+ return flowId;
+ }
+
+ public ParamFlowClusterConfig setFlowId(Long flowId) {
+ this.flowId = flowId;
+ return this;
+ }
+
+ public int getThresholdType() {
+ return thresholdType;
+ }
+
+ public ParamFlowClusterConfig setThresholdType(int thresholdType) {
+ this.thresholdType = thresholdType;
+ return this;
+ }
+
+ public boolean isFallbackToLocalWhenFail() {
+ return fallbackToLocalWhenFail;
+ }
+
+ public ParamFlowClusterConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) {
+ this.fallbackToLocalWhenFail = fallbackToLocalWhenFail;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+
+ ParamFlowClusterConfig that = (ParamFlowClusterConfig)o;
+
+ if (thresholdType != that.thresholdType) { return false; }
+ if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; }
+ return flowId != null ? flowId.equals(that.flowId) : that.flowId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = flowId != null ? flowId.hashCode() : 0;
+ result = 31 * result + thresholdType;
+ result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ParamFlowClusterConfig{" +
+ "flowId=" + flowId +
+ ", thresholdType=" + thresholdType +
+ ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail +
+ '}';
+ }
+}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java
index 51f7b16a..18f6db16 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRule.java
@@ -65,6 +65,9 @@ public class ParamFlowRule extends AbstractRule {
*/
private Map hotItems = new HashMap();
+ private boolean clusterMode = false;
+ private ParamFlowClusterConfig clusterConfig;
+
public int getGrade() {
return grade;
}
@@ -110,6 +113,25 @@ public class ParamFlowRule extends AbstractRule {
return this;
}
+ public boolean isClusterMode() {
+ return clusterMode;
+ }
+
+ public ParamFlowRule setClusterMode(boolean clusterMode) {
+ this.clusterMode = clusterMode;
+ return this;
+ }
+
+ public ParamFlowClusterConfig getClusterConfig() {
+ return clusterConfig;
+ }
+
+ public ParamFlowRule setClusterConfig(
+ ParamFlowClusterConfig clusterConfig) {
+ this.clusterConfig = clusterConfig;
+ return this;
+ }
+
@Override
@Deprecated
public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
@@ -126,8 +148,11 @@ public class ParamFlowRule extends AbstractRule {
if (grade != rule.grade) { return false; }
if (Double.compare(rule.count, count) != 0) { return false; }
+ if (clusterMode != rule.clusterMode) { return false; }
if (paramIdx != null ? !paramIdx.equals(rule.paramIdx) : rule.paramIdx != null) { return false; }
- return paramFlowItemList != null ? paramFlowItemList.equals(rule.paramFlowItemList) : rule.paramFlowItemList == null;
+ if (paramFlowItemList != null ? !paramFlowItemList.equals(rule.paramFlowItemList)
+ : rule.paramFlowItemList != null) { return false; }
+ return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null;
}
@Override
@@ -139,18 +164,20 @@ public class ParamFlowRule extends AbstractRule {
temp = Double.doubleToLongBits(count);
result = 31 * result + (int)(temp ^ (temp >>> 32));
result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0);
+ result = 31 * result + (clusterMode ? 1 : 0);
+ result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ParamFlowRule{" +
- "resource=" + getResource() +
- ", limitApp=" + getLimitApp() +
- ", grade=" + grade +
+ "grade=" + grade +
", paramIdx=" + paramIdx +
", count=" + count +
", paramFlowItemList=" + paramFlowItemList +
+ ", clusterMode=" + clusterMode +
+ ", clusterConfig=" + clusterConfig +
'}';
}
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java
index b10b260a..3f60b840 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManager.java
@@ -99,37 +99,6 @@ public final class ParamFlowRuleManager {
return rules;
}
- private static Object parseValue(String value, String classType) {
- if (value == null) {
- throw new IllegalArgumentException("Null value");
- }
- if (StringUtil.isBlank(classType)) {
- // If the class type is not provided, then treat it as string.
- return value;
- }
- // Handle primitive type.
- if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) {
- return Integer.parseInt(value);
- } else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) {
- return Boolean.parseBoolean(value);
- } else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) {
- return Long.parseLong(value);
- } else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) {
- return Double.parseDouble(value);
- } else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) {
- return Float.parseFloat(value);
- } else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) {
- return Byte.parseByte(value);
- } else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) {
- return Short.parseShort(value);
- } else if (char.class.toString().equals(classType)) {
- char[] array = value.toCharArray();
- return array.length > 0 ? array[0] : null;
- }
-
- return value;
- }
-
static class RulePropertyListener implements PropertyListener> {
@Override
@@ -163,7 +132,7 @@ public final class ParamFlowRuleManager {
}
for (ParamFlowRule rule : list) {
- if (!isValidRule(rule)) {
+ if (!ParamFlowRuleUtil.isValidRule(rule)) {
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule);
continue;
}
@@ -172,12 +141,7 @@ public final class ParamFlowRuleManager {
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
- if (rule.getParamFlowItemList() == null) {
- rule.setParamFlowItemList(new ArrayList());
- }
-
- Map itemMap = parseHotItems(rule.getParamFlowItemList());
- rule.setParsedHotItems(itemMap);
+ ParamFlowRuleUtil.fillExceptionFlowItems(rule);
String resourceName = rule.getResource();
List ruleList = newRuleMap.get(resourceName);
@@ -200,34 +164,6 @@ public final class ParamFlowRuleManager {
}
}
- static Map parseHotItems(List items) {
- Map itemMap = new HashMap();
- if (items == null || items.isEmpty()) {
- return itemMap;
- }
- for (ParamFlowItem item : items) {
- // Value should not be null.
- Object value;
- try {
- value = parseValue(item.getObject(), item.getClassType());
- } catch (Exception ex) {
- RecordLog.warn("[ParamFlowRuleManager] Failed to parse value for item: " + item, ex);
- continue;
- }
- if (item.getCount() == null || item.getCount() < 0 || value == null) {
- RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid exclusion parameter item: " + item);
- continue;
- }
- itemMap.put(value, item.getCount());
- }
- return itemMap;
- }
-
- static boolean isValidRule(ParamFlowRule rule) {
- return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
- && rule.getParamIdx() != null && rule.getParamIdx() >= 0;
- }
-
private ParamFlowRuleManager() {}
}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java
new file mode 100644
index 00000000..ec97b19c
--- /dev/null
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtil.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.slots.block.flow.param;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ */
+public final class ParamFlowRuleUtil {
+
+ public static boolean isValidRule(ParamFlowRule rule) {
+ return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0
+ && rule.getParamIdx() != null && rule.getParamIdx() >= 0 && checkCluster(rule);
+ }
+
+ private static boolean checkCluster(/*@PreChecked*/ ParamFlowRule rule) {
+ if (!rule.isClusterMode()) {
+ return true;
+ }
+ ParamFlowClusterConfig clusterConfig = rule.getClusterConfig();
+ return clusterConfig != null && validClusterRuleId(clusterConfig.getFlowId());
+ }
+
+ public static boolean validClusterRuleId(Long id) {
+ return id != null && id > 0;
+ }
+
+ public static void fillExceptionFlowItems(ParamFlowRule rule) {
+ if (rule != null) {
+ if (rule.getParamFlowItemList() == null) {
+ rule.setParamFlowItemList(new ArrayList());
+ }
+
+ Map itemMap = parseHotItems(rule.getParamFlowItemList());
+ rule.setParsedHotItems(itemMap);
+ }
+ }
+
+ static Map parseHotItems(List items) {
+ Map itemMap = new HashMap();
+ if (items == null || items.isEmpty()) {
+ return itemMap;
+ }
+ for (ParamFlowItem item : items) {
+ // Value should not be null.
+ Object value;
+ try {
+ value = parseItemValue(item.getObject(), item.getClassType());
+ } catch (Exception ex) {
+ RecordLog.warn("[ParamFlowRuleUtil] Failed to parse value for item: " + item, ex);
+ continue;
+ }
+ if (item.getCount() == null || item.getCount() < 0 || value == null) {
+ RecordLog.warn("[ParamFlowRuleUtil] Ignoring invalid exclusion parameter item: " + item);
+ continue;
+ }
+ itemMap.put(value, item.getCount());
+ }
+ return itemMap;
+ }
+
+ static Object parseItemValue(String value, String classType) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null value");
+ }
+ if (StringUtil.isBlank(classType)) {
+ // If the class type is not provided, then treat it as string.
+ return value;
+ }
+ // Handle primitive type.
+ if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) {
+ return Integer.parseInt(value);
+ } else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) {
+ return Boolean.parseBoolean(value);
+ } else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) {
+ return Long.parseLong(value);
+ } else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) {
+ return Double.parseDouble(value);
+ } else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) {
+ return Float.parseFloat(value);
+ } else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) {
+ return Byte.parseByte(value);
+ } else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) {
+ return Short.parseShort(value);
+ } else if (char.class.toString().equals(classType)) {
+ char[] array = value.toCharArray();
+ return array.length > 0 ? array[0] : null;
+ }
+
+ return value;
+ }
+
+ private ParamFlowRuleUtil() {}
+}
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java
index 6f27a934..a6ae69c0 100644
--- a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleManagerTest.java
@@ -15,11 +15,9 @@
*/
package com.alibaba.csp.sentinel.slots.block.flow.param;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
@@ -109,84 +107,4 @@ public class ParamFlowRuleManagerTest {
assertTrue(allRules.contains(ruleC));
assertTrue(allRules.contains(ruleD));
}
-
- @Test
- public void testParseHotParamExceptionItemsFailure() {
- String valueB = "Sentinel";
- Integer valueC = 6;
- char valueD = 6;
- float valueE = 11.11f;
- // Null object will not be parsed.
- ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName());
- // Hot item with empty class type will be treated as string.
- ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null);
- ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, "");
- // Bad count will not be parsed.
- ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5);
- ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName());
-
- List badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE);
- Map parsedItems = ParamFlowRuleManager.parseHotItems(badItems);
-
- // Value B and E will be parsed, but ignoring the type.
- assertEquals(2, parsedItems.size());
- assertEquals(itemB.getCount(), parsedItems.get(valueB));
- assertFalse(parsedItems.containsKey(valueE));
- assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE)));
- }
-
- @Test
- public void testParseHotParamExceptionItemsSuccess() {
- // Test for empty list.
- assertEquals(0, ParamFlowRuleManager.parseHotItems(null).size());
- assertEquals(0, ParamFlowRuleManager.parseHotItems(new ArrayList()).size());
-
- // Test for boxing objects and primitive types.
- Double valueA = 1.1d;
- String valueB = "Sentinel";
- Integer valueC = 6;
- char valueD = 'c';
- ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1);
- ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3);
- ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5);
- ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD))
- .setClassType(char.class.getName())
- .setCount(7);
- List items = Arrays.asList(itemA, itemB, itemC, itemD);
- Map parsedItems = ParamFlowRuleManager.parseHotItems(items);
- assertEquals(itemA.getCount(), parsedItems.get(valueA));
- assertEquals(itemB.getCount(), parsedItems.get(valueB));
- assertEquals(itemC.getCount(), parsedItems.get(valueC));
- assertEquals(itemD.getCount(), parsedItems.get(valueD));
- }
-
- @Test
- public void testCheckValidHotParamRule() {
- // Null or empty resource;
- ParamFlowRule rule1 = new ParamFlowRule();
- ParamFlowRule rule2 = new ParamFlowRule("");
- assertFalse(ParamFlowRuleManager.isValidRule(null));
- assertFalse(ParamFlowRuleManager.isValidRule(rule1));
- assertFalse(ParamFlowRuleManager.isValidRule(rule2));
-
- // Invalid threshold count.
- ParamFlowRule rule3 = new ParamFlowRule("abc")
- .setCount(-1)
- .setParamIdx(1);
- assertFalse(ParamFlowRuleManager.isValidRule(rule3));
-
- // Parameter index not set or invalid.
- ParamFlowRule rule4 = new ParamFlowRule("abc")
- .setCount(1);
- ParamFlowRule rule5 = new ParamFlowRule("abc")
- .setCount(1)
- .setParamIdx(-1);
- assertFalse(ParamFlowRuleManager.isValidRule(rule4));
- assertFalse(ParamFlowRuleManager.isValidRule(rule5));
-
- ParamFlowRule goodRule = new ParamFlowRule("abc")
- .setCount(10)
- .setParamIdx(1);
- assertTrue(ParamFlowRuleManager.isValidRule(goodRule));
- }
}
\ No newline at end of file
diff --git a/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java
new file mode 100644
index 00000000..af49f476
--- /dev/null
+++ b/sentinel-extension/sentinel-parameter-flow-control/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/param/ParamFlowRuleUtilTest.java
@@ -0,0 +1,95 @@
+package com.alibaba.csp.sentinel.slots.block.flow.param;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class ParamFlowRuleUtilTest {
+ @Test
+ public void testCheckValidHotParamRule() {
+ // Null or empty resource;
+ ParamFlowRule rule1 = new ParamFlowRule();
+ ParamFlowRule rule2 = new ParamFlowRule("");
+ assertFalse(ParamFlowRuleUtil.isValidRule(null));
+ assertFalse(ParamFlowRuleUtil.isValidRule(rule1));
+ assertFalse(ParamFlowRuleUtil.isValidRule(rule2));
+
+ // Invalid threshold count.
+ ParamFlowRule rule3 = new ParamFlowRule("abc")
+ .setCount(-1)
+ .setParamIdx(1);
+ assertFalse(ParamFlowRuleUtil.isValidRule(rule3));
+
+ // Parameter index not set or invalid.
+ ParamFlowRule rule4 = new ParamFlowRule("abc")
+ .setCount(1);
+ ParamFlowRule rule5 = new ParamFlowRule("abc")
+ .setCount(1)
+ .setParamIdx(-1);
+ assertFalse(ParamFlowRuleUtil.isValidRule(rule4));
+ assertFalse(ParamFlowRuleUtil.isValidRule(rule5));
+
+ ParamFlowRule goodRule = new ParamFlowRule("abc")
+ .setCount(10)
+ .setParamIdx(1);
+ assertTrue(ParamFlowRuleUtil.isValidRule(goodRule));
+ }
+
+ @Test
+ public void testParseHotParamExceptionItemsFailure() {
+ String valueB = "Sentinel";
+ Integer valueC = 6;
+ char valueD = 6;
+ float valueE = 11.11f;
+ // Null object will not be parsed.
+ ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName());
+ // Hot item with empty class type will be treated as string.
+ ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null);
+ ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, "");
+ // Bad count will not be parsed.
+ ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5);
+ ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName());
+
+ List badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE);
+ Map parsedItems = ParamFlowRuleUtil.parseHotItems(badItems);
+
+ // Value B and E will be parsed, but ignoring the type.
+ assertEquals(2, parsedItems.size());
+ assertEquals(itemB.getCount(), parsedItems.get(valueB));
+ assertFalse(parsedItems.containsKey(valueE));
+ assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE)));
+ }
+
+ @Test
+ public void testParseHotParamExceptionItemsSuccess() {
+ // Test for empty list.
+ assertEquals(0, ParamFlowRuleUtil.parseHotItems(null).size());
+ assertEquals(0, ParamFlowRuleUtil.parseHotItems(new ArrayList()).size());
+
+ // Test for boxing objects and primitive types.
+ Double valueA = 1.1d;
+ String valueB = "Sentinel";
+ Integer valueC = 6;
+ char valueD = 'c';
+ ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1);
+ ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3);
+ ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5);
+ ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD))
+ .setClassType(char.class.getName())
+ .setCount(7);
+ List items = Arrays.asList(itemA, itemB, itemC, itemD);
+ Map parsedItems = ParamFlowRuleUtil.parseHotItems(items);
+ assertEquals(itemA.getCount(), parsedItems.get(valueA));
+ assertEquals(itemB.getCount(), parsedItems.get(valueB));
+ assertEquals(itemC.getCount(), parsedItems.get(valueC));
+ assertEquals(itemD.getCount(), parsedItems.get(valueD));
+ }
+}
\ No newline at end of file
From b3ae7f58bf8c1878c9fceee3caf965bba59d8895 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Fri, 23 Nov 2018 17:53:36 +0800
Subject: [PATCH 04/20] Add common module for Sentinel default cluster
implementation
- Add a `ClusterTransportClient` for transport abstraction of Sentinel cluster
- Add universal request/response interface and common ClusterRequest/ClusterResponse abstraction
- Add common request/response data entity
- Add basic abstraction of codec (EntityWriter and EntityDecoder)
Signed-off-by: Eric Zhao
---
.../sentinel/cluster/ClusterConstants.java | 44 ++++++++++
.../cluster/ClusterErrorMessages.java | 32 +++++++
.../cluster/ClusterTransportClient.java | 37 ++++++++
.../sentinel/cluster/codec/EntityDecoder.java | 33 +++++++
.../sentinel/cluster/codec/EntityWriter.java | 35 ++++++++
.../codec/request/RequestEntityDecoder.java | 27 ++++++
.../codec/request/RequestEntityWriter.java | 29 +++++++
.../codec/response/ResponseEntityDecoder.java | 27 ++++++
.../codec/response/ResponseEntityWriter.java | 29 +++++++
.../exception/SentinelClusterException.java | 32 +++++++
.../cluster/request/ClusterRequest.java | 79 +++++++++++++++++
.../csp/sentinel/cluster/request/Request.java | 39 +++++++++
.../cluster/request/data/FlowRequestData.java | 63 ++++++++++++++
.../request/data/ParamFlowRequestData.java | 65 ++++++++++++++
.../cluster/response/ClusterResponse.java | 87 +++++++++++++++++++
.../sentinel/cluster/response/Response.java | 46 ++++++++++
.../response/data/FlowTokenResponseData.java | 52 +++++++++++
.../alibaba/csp/sentinel/util/SpiLoader.java | 49 +++++++++++
18 files changed, 805 insertions(+)
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java
create mode 100644 sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java
create mode 100644 sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java
new file mode 100644
index 00000000..0183ab72
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterConstants {
+
+ public static final int MSG_TYPE_PING = 0;
+ public static final int MSG_TYPE_FLOW = 1;
+ public static final int MSG_TYPE_PARAM_FLOW = 2;
+
+ public static final int RESPONSE_STATUS_BAD = -1;
+ public static final int RESPONSE_STATUS_OK = 0;
+
+ public static final int PARAM_TYPE_INTEGER = 0;
+ public static final int PARAM_TYPE_LONG = 1;
+ public static final int PARAM_TYPE_BYTE = 2;
+ public static final int PARAM_TYPE_DOUBLE = 3;
+ public static final int PARAM_TYPE_FLOAT = 4;
+ public static final int PARAM_TYPE_SHORT = 5;
+ public static final int PARAM_TYPE_BOOLEAN = 6;
+ public static final int PARAM_TYPE_STRING = 7;
+
+ public static final int DEFAULT_CLUSTER_SERVER_PORT = 8730;
+ public static final int DEFAULT_REQUEST_TIMEOUT = 20;
+
+ private ClusterConstants() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java
new file mode 100644
index 00000000..3e6ad416
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterErrorMessages.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+/**
+ * @author jialiang.ljl
+ * @since 1.4.0
+ */
+public final class ClusterErrorMessages {
+
+ public static final String BAD_REQUEST = "bad request";
+ public static final String UNEXPECTED_STATUS = "unexpected status";
+ public static final String TOO_MANY_REQUESTS = "too many requests (client side)";
+ public static final String REQUEST_TIME_OUT = "request time out";
+ public static final String CLIENT_NOT_READY = "client not ready (not running or initializing)";
+ public static final String NO_RULES_IN_SERVER = "no rules in token server";
+
+ private ClusterErrorMessages() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
new file mode 100644
index 00000000..26af3c20
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster;
+
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+
+/**
+ * Synchronous transport client for distributed flow control.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ClusterTransportClient {
+
+ /**
+ * Send request to remote server and get response.
+ *
+ * @param request Sentinel cluster request
+ * @return response from remote server
+ * @throws Exception some error occurs
+ */
+ ClusterResponse sendRequest(ClusterRequest request) throws Exception;
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java
new file mode 100644
index 00000000..1bb830ac
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityDecoder.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec;
+
+/**
+ * @param source stream type
+ * @param target entity type
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface EntityDecoder {
+
+ /**
+ * Decode target object from source stream.
+ *
+ * @param source source stream
+ * @return decoded target object
+ */
+ T decode(S source);
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java
new file mode 100644
index 00000000..03dfcbaf
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/EntityWriter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec;
+
+/**
+ * A universal interface for publishing entities to a target stream.
+ *
+ * @param entity type
+ * @param target stream type
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface EntityWriter {
+
+ /**
+ * Write the provided entity to target stream.
+ *
+ * @param entity entity to publish
+ * @param target the target stream
+ */
+ void writeTo(E entity, T target);
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java
new file mode 100644
index 00000000..8b9fd33f
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityDecoder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec.request;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface RequestEntityDecoder extends EntityDecoder {
+
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java
new file mode 100644
index 00000000..814bbed1
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/request/RequestEntityWriter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec.request;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+
+/**
+ * A universal {@link EntityWriter} interface for publishing {@link Request} to a target stream.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface RequestEntityWriter extends EntityWriter {
+
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java
new file mode 100644
index 00000000..6bb03ae1
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityDecoder.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec.response;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.response.Response;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ResponseEntityDecoder extends EntityDecoder {
+
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java
new file mode 100644
index 00000000..89e5625c
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/codec/response/ResponseEntityWriter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.codec.response;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.response.Response;
+
+/**
+ * A universal {@link EntityWriter} interface for publishing {@link Response} to a target stream.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ResponseEntityWriter extends EntityWriter {
+
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java
new file mode 100644
index 00000000..ddc73095
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/exception/SentinelClusterException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.exception;
+
+/**
+ * @author jialiang.ljl
+ * @since 1.4.0
+ */
+public class SentinelClusterException extends Exception {
+
+ public SentinelClusterException(String errorMsg) {
+ super(errorMsg);
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java
new file mode 100644
index 00000000..2e513e41
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/ClusterRequest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.request;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterRequest implements Request {
+
+ private int id;
+ private int type;
+
+ private T data;
+
+ public ClusterRequest() {}
+
+ public ClusterRequest(int id, int type, T data) {
+ this.id = id;
+ this.type = type;
+ this.data = data;
+ }
+
+ public ClusterRequest(int type, T data) {
+ this.type = type;
+ this.data = data;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ public ClusterRequest setId(int id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ public ClusterRequest setType(int type) {
+ this.type = type;
+ return this;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public ClusterRequest setData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ClusterRequest{" +
+ "id=" + id +
+ ", type=" + type +
+ ", data=" + data +
+ '}';
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java
new file mode 100644
index 00000000..cda5b9c0
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/Request.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.request;
+
+/**
+ * Cluster transport request interface.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface Request {
+
+ /**
+ * Get request type.
+ *
+ * @return request type
+ */
+ int getType();
+
+ /**
+ * Get request ID.
+ *
+ * @return unique request ID
+ */
+ int getId();
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java
new file mode 100644
index 00000000..ab401983
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/FlowRequestData.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.request.data;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowRequestData {
+
+ private long flowId;
+ private int count;
+ private boolean priority;
+
+ public long getFlowId() {
+ return flowId;
+ }
+
+ public FlowRequestData setFlowId(long flowId) {
+ this.flowId = flowId;
+ return this;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public FlowRequestData setCount(int count) {
+ this.count = count;
+ return this;
+ }
+
+ public boolean isPriority() {
+ return priority;
+ }
+
+ public FlowRequestData setPriority(boolean priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "FlowRequestData{" +
+ "flowId=" + flowId +
+ ", count=" + count +
+ ", priority=" + priority +
+ '}';
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java
new file mode 100644
index 00000000..c6ac6ec4
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/request/data/ParamFlowRequestData.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.request.data;
+
+import java.util.Collection;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ParamFlowRequestData {
+
+ private long flowId;
+ private int count;
+ private Collection params;
+
+ public long getFlowId() {
+ return flowId;
+ }
+
+ public ParamFlowRequestData setFlowId(long flowId) {
+ this.flowId = flowId;
+ return this;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public ParamFlowRequestData setCount(int count) {
+ this.count = count;
+ return this;
+ }
+
+ public Collection getParams() {
+ return params;
+ }
+
+ public ParamFlowRequestData setParams(Collection params) {
+ this.params = params;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ParamFlowRequestData{" +
+ "flowId=" + flowId +
+ ", count=" + count +
+ ", params=" + params +
+ '}';
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java
new file mode 100644
index 00000000..4f5ebf5b
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/ClusterResponse.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.response;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterResponse implements Response {
+
+ private int id;
+ private int type;
+ private int status;
+
+ private T data;
+
+ public ClusterResponse() {}
+
+ public ClusterResponse(int id, int type, int status, T data) {
+ this.id = id;
+ this.type = type;
+ this.status = status;
+ this.data = data;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ public ClusterResponse setId(int id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ public ClusterResponse setType(int type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public int getStatus() {
+ return status;
+ }
+
+ public ClusterResponse setStatus(int status) {
+ this.status = status;
+ return this;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public ClusterResponse setData(T data) {
+ this.data = data;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ClusterResponse{" +
+ "id=" + id +
+ ", type=" + type +
+ ", status=" + status +
+ ", data=" + data +
+ '}';
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java
new file mode 100644
index 00000000..037088d9
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/Response.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.response;
+
+/**
+ * Cluster transport response interface.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface Response {
+
+ /**
+ * Get response ID.
+ *
+ * @return response ID
+ */
+ int getId();
+
+ /**
+ * Get response type.
+ *
+ * @return response type
+ */
+ int getType();
+
+ /**
+ * Get response status.
+ *
+ * @return response status
+ */
+ int getStatus();
+}
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java
new file mode 100644
index 00000000..2ca2842c
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.response.data;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowTokenResponseData {
+
+ private int remainingCount;
+ private int waitInMs;
+
+ public int getRemainingCount() {
+ return remainingCount;
+ }
+
+ public FlowTokenResponseData setRemainingCount(int remainingCount) {
+ this.remainingCount = remainingCount;
+ return this;
+ }
+
+ public int getWaitInMs() {
+ return waitInMs;
+ }
+
+ public FlowTokenResponseData setWaitInMs(int waitInMs) {
+ this.waitInMs = waitInMs;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "FlowTokenResponseData{" +
+ "remainingCount=" + remainingCount +
+ ", waitInMs=" + waitInMs +
+ '}';
+ }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
new file mode 100644
index 00000000..b057fc55
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.util;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class SpiLoader {
+
+ private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap();
+
+ public static T loadFirstInstance(Class clazz) {
+ String key = clazz.getName();
+ // Not thread-safe, as it's expected to be resolved in a thread-safe context.
+ ServiceLoader serviceLoader = SERVICE_LOADER_MAP.get(key);
+ if (serviceLoader == null) {
+ serviceLoader = ServiceLoader.load(clazz);
+ SERVICE_LOADER_MAP.put(key, serviceLoader);
+ }
+
+ Iterator iterator = serviceLoader.iterator();
+ if (iterator.hasNext()) {
+ return iterator.next();
+ } else {
+ return null;
+ }
+ }
+
+ private SpiLoader() {}
+}
From 6caa19ea95bb723752a6c6b5f746c910b965e118 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Fri, 23 Nov 2018 19:35:06 +0800
Subject: [PATCH 05/20] Add default Sentinel cluster transport implementation
with Netty
- Add Netty transport client implementation and default cluster token client
- Add config manager for cluster client
- Add codec SPI mechanism for client entity decoder / writer
Signed-off-by: Eric Zhao
---
.../sentinel-cluster-client-default/README.md | 1 +
.../cluster/client/ClientConstants.java | 32 +++
.../client/DefaultClusterTokenClient.java | 105 +++++++++
.../cluster/client/NettyTransportClient.java | 208 ++++++++++++++++++
.../codec/ClientEntityCodecProvider.java | 62 ++++++
.../codec/DefaultRequestEntityWriter.java | 54 +++++
.../codec/DefaultResponseEntityDecoder.java | 66 ++++++
.../codec/data/FlowRequestDataWriter.java | 39 ++++
.../codec/data/FlowResponseDataDecoder.java | 39 ++++
.../data/ParamFlowRequestDataWriter.java | 128 +++++++++++
.../codec/netty/NettyRequestEncoder.java | 44 ++++
.../codec/netty/NettyResponseDecoder.java | 54 +++++
.../registry/RequestDataWriterRegistry.java | 47 ++++
.../registry/ResponseDataDecodeRegistry.java | 48 ++++
.../client/config/ClusterClientConfig.java | 65 ++++++
.../config/ClusterClientConfigManager.java | 30 +++
.../client/handler/TokenClientHandler.java | 72 ++++++
.../handler/TokenClientPromiseHolder.java | 60 +++++
....cluster.codec.request.RequestEntityWriter | 1 +
...uster.codec.response.ResponseEntityDecoder | 1 +
20 files changed, 1156 insertions(+)
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/README.md
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder
diff --git a/sentinel-cluster/sentinel-cluster-client-default/README.md b/sentinel-cluster/sentinel-cluster-client-default/README.md
new file mode 100644
index 00000000..d09d8263
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/README.md
@@ -0,0 +1 @@
+# Sentinel Cluster Client (Default)
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java
new file mode 100644
index 00000000..9645feb5
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/ClientConstants.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client;
+
+/**
+ * @author Eric Zhao
+ */
+public final class ClientConstants {
+
+ public static final int TYPE_PING = 0;
+ public static final int TYPE_FLOW = 1;
+ public static final int TYPE_PARAM_FLOW = 2;
+
+ public static final int CLIENT_STATUS_OFF = 0;
+ public static final int CLIENT_STATUS_PENDING = 1;
+ public static final int CLIENT_STATUS_STARTED = 2;
+
+ private ClientConstants() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
new file mode 100644
index 00000000..668f43bd
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client;
+
+import java.util.Collection;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.cluster.ClusterTokenClient;
+import com.alibaba.csp.sentinel.cluster.ClusterTransportClient;
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
+import com.alibaba.csp.sentinel.cluster.log.ClusterStatLogUtil;
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
+import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultClusterTokenClient implements ClusterTokenClient {
+
+ private ClusterTransportClient transportClient;
+
+ public DefaultClusterTokenClient() {
+ // TODO: load and create transport client here.
+ }
+
+ public DefaultClusterTokenClient(ClusterTransportClient transportClient) {
+ this.transportClient = transportClient;
+ }
+
+ @Override
+ public TokenServerDescriptor currentServer() {
+ return new TokenServerDescriptor();
+ }
+
+ @Override
+ public TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) {
+ if (notValidRequest(flowId, acquireCount)) {
+ return badRequest();
+ }
+ FlowRequestData data = new FlowRequestData().setCount(acquireCount)
+ .setFlowId(flowId).setPriority(prioritized);
+ ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data);
+ try {
+ return sendTokenRequest(request);
+ } catch (Exception ex) {
+ ClusterStatLogUtil.log(ex.getMessage());
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+ }
+
+ @Override
+ public TokenResult requestParamToken(Long flowId, int acquireCount, Collection params) {
+ if (notValidRequest(flowId, acquireCount) || params == null || params.isEmpty()) {
+ return badRequest();
+ }
+ ParamFlowRequestData data = new ParamFlowRequestData().setCount(acquireCount)
+ .setFlowId(flowId).setParams(params);
+ ClusterRequest request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_PARAM_FLOW, data);
+ try {
+ return sendTokenRequest(request);
+ } catch (Exception ex) {
+ ClusterStatLogUtil.log(ex.getMessage());
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+ }
+
+ private boolean notValidRequest(Long id, int count) {
+ return id == null || id <= 0 || count <= 0;
+ }
+
+ private TokenResult badRequest() {
+ return new TokenResult(TokenResultStatus.BAD_REQUEST);
+ }
+
+ private TokenResult sendTokenRequest(ClusterRequest request) throws Exception {
+ ClusterResponse response = transportClient.sendRequest(request);
+ TokenResult result = new TokenResult(response.getStatus());
+ if (response.getData() != null) {
+ FlowTokenResponseData responseData = (FlowTokenResponseData)response.getData();
+ result.setRemaining(responseData.getRemainingCount())
+ .setWaitInMs(responseData.getWaitInMs());
+ }
+ return result;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
new file mode 100644
index 00000000..676f4d44
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.csp.sentinel.cluster.ClusterErrorMessages;
+import com.alibaba.csp.sentinel.cluster.ClusterTransportClient;
+import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder;
+import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
+import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler;
+import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder;
+import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException;
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.util.concurrent.GenericFutureListener;
+
+/**
+ * Netty transport client implementation for Sentinel cluster transport.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class NettyTransportClient implements ClusterTransportClient {
+
+ private ClusterClientConfig clientConfig;
+ private String host;
+ private int port;
+
+ private Channel channel;
+ private NioEventLoopGroup eventLoopGroup;
+ private TokenClientHandler clientHandler;
+
+ private AtomicInteger idGenerator = new AtomicInteger(0);
+
+ private AtomicInteger failConnectedTime = new AtomicInteger(0);
+
+ public NettyTransportClient(ClusterClientConfig clientConfig, String host, int port) {
+ this.clientConfig = clientConfig;
+ this.host = host;
+ this.port = port;
+ }
+
+ private Bootstrap initClientBootstrap() {
+ Bootstrap b = new Bootstrap();
+ eventLoopGroup = new NioEventLoopGroup();
+ b.group(eventLoopGroup)
+ .channel(NioSocketChannel.class)
+ .option(ChannelOption.SO_TIMEOUT, 20)
+ .option(ChannelOption.TCP_NODELAY, true)
+ .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
+ .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
+ .handler(new ChannelInitializer() {
+ @Override
+ public void initChannel(SocketChannel ch) throws Exception {
+ clientHandler = new TokenClientHandler();
+ ChannelPipeline pipeline = ch.pipeline();
+ pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
+ pipeline.addLast(new NettyResponseDecoder());
+ pipeline.addLast(new LengthFieldPrepender(2));
+ pipeline.addLast(new NettyRequestEncoder());
+ pipeline.addLast(clientHandler);
+ }
+ });
+
+ return b;
+ }
+
+ private void connect(Bootstrap b) {
+ b.connect(host, port).addListener(new GenericFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) {
+ if (future.cause() != null) {
+ RecordLog.warn(
+ "[NettyTransportClient] Could not connect after " + failConnectedTime.get() + " times",
+ future.cause());
+ failConnectedTime.incrementAndGet();
+ channel = null;
+ } else {
+ failConnectedTime.set(0);
+ channel = future.channel();
+ RecordLog.info("[NettyTransportClient] Successfully connect to server " + host + ":" + port);
+ }
+ }
+ });
+ }
+
+ public void start() {
+ connect(initClientBootstrap());
+ }
+
+ public void stop() {
+ if (channel != null) {
+ channel.close();
+ channel = null;
+ }
+ if (eventLoopGroup != null) {
+ eventLoopGroup.shutdownGracefully();
+ }
+ failConnectedTime.set(0);
+
+ RecordLog.info("[NettyTransportClient] Token client stopped");
+ }
+
+ private boolean validRequest(Request request) {
+ return request != null && request.getType() >= 0;
+ }
+
+ public boolean isReady() {
+ return channel != null && clientHandler != null && clientHandler.hasStarted();
+ }
+
+ @Override
+ public ClusterResponse sendRequest(ClusterRequest request) throws Exception {
+ if (!isReady()) {
+ throw new SentinelClusterException(ClusterErrorMessages.CLIENT_NOT_READY);
+ }
+ if (!validRequest(request)) {
+ throw new SentinelClusterException(ClusterErrorMessages.BAD_REQUEST);
+ }
+ int xid = getCurrentId();
+ try {
+ request.setId(xid);
+
+ channel.writeAndFlush(request);
+
+ ChannelPromise promise = channel.newPromise();
+ TokenClientPromiseHolder.putPromise(xid, promise);
+
+ // TODO: timeout
+ if (!promise.await(20)) {
+ throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT);
+ }
+
+ SimpleEntry entry = TokenClientPromiseHolder.getEntry(xid);
+ if (entry == null || entry.getValue() == null) {
+ // Should not go through here.
+ throw new SentinelClusterException(ClusterErrorMessages.UNEXPECTED_STATUS);
+ }
+ return entry.getValue();
+ } finally {
+ TokenClientPromiseHolder.remove(xid);
+ }
+ }
+
+ private int getCurrentId() {
+ if (idGenerator.get() > MAX_ID) {
+ idGenerator.set(0);
+ }
+ return idGenerator.incrementAndGet();
+ }
+
+ /*public CompletableFuture sendRequestAsync(ClusterRequest request) {
+ // Uncomment this when min target JDK is 1.8.
+ if (!validRequest(request)) {
+ return CompletableFuture.failedFuture(new IllegalArgumentException("Bad request"));
+ }
+ int xid = getCurrentId();
+ request.setId(xid);
+
+ CompletableFuture future = new CompletableFuture<>();
+ channel.writeAndFlush(request)
+ .addListener(f -> {
+ if (f.isSuccess()) {
+ future.complete(someResult);
+ } else if (f.cause() != null) {
+ future.completeExceptionally(f.cause());
+ } else {
+ future.cancel(false);
+ }
+ });
+ return future;
+ }*/
+
+ private static final int MAX_ID = 999_999_999;
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java
new file mode 100644
index 00000000..6cb0ebda
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec;
+
+import com.alibaba.csp.sentinel.util.SpiLoader;
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClientEntityCodecProvider {
+
+ private static RequestEntityWriter requestEntityWriter = null;
+ private static ResponseEntityDecoder responseEntityDecoder = null;
+
+ static {
+ resolveInstance();
+ }
+
+ private static void resolveInstance() {
+ RequestEntityWriter writer = SpiLoader.loadFirstInstance(RequestEntityWriter.class);
+ if (writer == null) {
+ RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed");
+ } else {
+ requestEntityWriter = writer;
+ RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: " + requestEntityWriter.getClass().getCanonicalName());
+ }
+ ResponseEntityDecoder decoder = SpiLoader.loadFirstInstance(ResponseEntityDecoder.class);
+ if (decoder == null) {
+ RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed");
+ } else {
+ responseEntityDecoder = decoder;
+ RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: " + responseEntityDecoder.getClass().getCanonicalName());
+ }
+ }
+
+ public static RequestEntityWriter getRequestEntityWriter() {
+ return requestEntityWriter;
+ }
+
+ public static ResponseEntityDecoder getResponseEntityDecoder() {
+ return responseEntityDecoder;
+ }
+
+ private ClientEntityCodecProvider() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
new file mode 100644
index 00000000..c4a3ce91
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec;
+
+import com.alibaba.csp.sentinel.cluster.client.codec.registry.RequestDataWriterRegistry;
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultRequestEntityWriter implements RequestEntityWriter {
+
+ @Override
+ public void writeTo(ClusterRequest request, ByteBuf target) {
+ int type = request.getType();
+ EntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type);
+
+ if (requestDataWriter == null) {
+ // TODO: may need to throw exception?
+ RecordLog.warn(
+ "[NettyRequestEncoder] Cannot find matching request writer for type <{0}>, dropping the request", type);
+ return;
+ }
+ // Write head part of request.
+ writeHead(request, target);
+ // Write data part.
+ requestDataWriter.writeTo(request.getData(), target);
+ }
+
+ private void writeHead(Request request, ByteBuf out) {
+ out.writeInt(request.getId());
+ out.writeByte(request.getType());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java
new file mode 100644
index 00000000..4c1ad34e
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec;
+
+import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * Default entity decoder for any {@link ClusterResponse} entity.
+ *
+ * Decode format:
+ *
+ * +--------+---------+-----------+---------+
+ * | xid(4) | type(1) | status(1) | data... |
+ * +--------+---------+-----------+---------+
+ *
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultResponseEntityDecoder implements ResponseEntityDecoder {
+
+ @Override
+ public ClusterResponse decode(ByteBuf source) {
+ if (source.readableBytes() >= 6) {
+ int xid = source.readInt();
+ int type = source.readByte();
+ int status = source.readByte();
+
+ EntityDecoder decoder = ResponseDataDecodeRegistry.getDecoder(type);
+ if (decoder == null) {
+ RecordLog.warn("Unknown type of response data decoder: {0}", type);
+ return null;
+ }
+
+ Object data;
+ if (source.readableBytes() == 0) {
+ data = null;
+ } else {
+ // TODO: handle decode error here.
+ data = decoder.decode(source);
+ }
+
+ return new ClusterResponse<>(xid, type, status, data);
+ }
+ return null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java
new file mode 100644
index 00000000..2683a925
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.data;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * +-------------------+--------------+----------------+---------------+------------------+
+ * | RequestID(4 byte) | Type(1 byte) | FlowID(4 byte) | Count(4 byte) | PriorityFlag (1) |
+ * +-------------------+--------------+----------------+---------------+------------------+
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowRequestDataWriter implements EntityWriter {
+
+ @Override
+ public void writeTo(FlowRequestData entity, ByteBuf target) {
+ target.writeLong(entity.getFlowId());
+ target.writeInt(entity.getCount());
+ target.writeBoolean(entity.isPriority());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java
new file mode 100644
index 00000000..c15355fe
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.data;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowResponseDataDecoder implements EntityDecoder {
+
+ @Override
+ public FlowTokenResponseData decode(ByteBuf source) {
+ FlowTokenResponseData data = new FlowTokenResponseData();
+
+ if (source.readableBytes() == 8) {
+ data.setRemainingCount(source.readInt());
+ data.setWaitInMs(source.readInt());
+ }
+ return data;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
new file mode 100644
index 00000000..7367b6e2
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/ParamFlowRequestDataWriter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.data;
+
+import java.util.Collection;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ParamFlowRequestDataWriter implements EntityWriter {
+
+ @Override
+ public void writeTo(ParamFlowRequestData entity, ByteBuf target) {
+ target.writeLong(entity.getFlowId());
+ target.writeInt(entity.getCount());
+
+ Collection params = entity.getParams();
+
+ // Write parameter amount.
+ int amount = calculateParamAmount(params);
+ target.writeInt(amount);
+
+ // Serialize parameters with type flag.
+ for (Object param : entity.getParams()) {
+ encodeValue(param, target);
+ }
+ }
+
+ private void encodeValue(Object param, ByteBuf target) {
+ // Handle primitive type.
+ if (param instanceof Integer || int.class.isInstance(param)) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_INTEGER);
+ target.writeInt((Integer)param);
+ } else if (param instanceof String) {
+ encodeString((String)param, target);
+ } else if (boolean.class.isInstance(param) || param instanceof Boolean) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_BOOLEAN);
+ target.writeBoolean((Boolean)param);
+ } else if (long.class.isInstance(param) || param instanceof Long) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_LONG);
+ target.writeLong((Long)param);
+ } else if (double.class.isInstance(param) || param instanceof Double) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_DOUBLE);
+ target.writeDouble((Double)param);
+ } else if (float.class.isInstance(param) || param instanceof Float) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_FLOAT);
+ target.writeFloat((Float)param);
+ } else if (byte.class.isInstance(param) || param instanceof Byte) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_BYTE);
+ target.writeByte((Byte)param);
+ } else if (short.class.isInstance(param) || param instanceof Short) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_SHORT);
+ target.writeShort((Short)param);
+ } else {
+ // Unexpected type, drop.
+ }
+ }
+
+ private void encodeString(String param, ByteBuf target) {
+ target.writeByte(ClusterConstants.PARAM_TYPE_STRING);
+ byte[] tmpChars = param.getBytes();
+ target.writeInt(tmpChars.length);
+ target.writeBytes(tmpChars);
+ }
+
+ private int calculateParamAmount(/*@NonEmpty*/ Collection params) {
+ int size = 0;
+ int length = 0;
+ for (Object param : params) {
+ int s = calculateParamTransportSize(param);
+ if (size + s > PARAM_MAX_SIZE) {
+ break;
+ }
+ length++;
+ }
+ return length;
+ }
+
+ private int calculateParamTransportSize(Object value) {
+ // Layout for primitives: |type flag(1)|value|
+ // size = original size + type flag (1)
+ if (value instanceof Integer || int.class.isInstance(value)) {
+ return 5;
+ } else if (value instanceof String) {
+ // Layout for string: |type flag(1)|length(4)|string content|
+ String tmpValue = (String)value;
+ byte[] tmpChars = tmpValue.getBytes();
+ return 1 + 4 + tmpChars.length;
+ } else if (boolean.class.isInstance(value) || value instanceof Boolean) {
+ return 2;
+ } else if (long.class.isInstance(value) || value instanceof Long) {
+ return 9;
+ } else if (double.class.isInstance(value) || value instanceof Double) {
+ return 9;
+ } else if (float.class.isInstance(value) || value instanceof Float) {
+ return 5;
+ } else if (byte.class.isInstance(value) || value instanceof Byte) {
+ return 2;
+ } else if (short.class.isInstance(value) || value instanceof Short) {
+ return 3;
+ } else {
+ // Ignore unexpected type.
+ return 0;
+ }
+ }
+
+ private static final int PARAM_MAX_SIZE = 1000;
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
new file mode 100644
index 00000000..bc8b05cb
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.netty;
+
+import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider;
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter;
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+/**
+ * @author Eric Zhao
+ */
+public class NettyRequestEncoder extends MessageToByteEncoder {
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, ClusterRequest request, ByteBuf out) throws Exception {
+ RequestEntityWriter requestEntityWriter = ClientEntityCodecProvider.getRequestEntityWriter();
+ if (requestEntityWriter == null) {
+ // TODO: may need to throw exception?
+ RecordLog.warn("[NettyRequestEncoder] Cannot resolve the global request entity writer, dropping the request");
+ return;
+ }
+
+ requestEntityWriter.writeTo(request, out);
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java
new file mode 100644
index 00000000..75c15a72
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyResponseDecoder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.netty;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.client.codec.ClientEntityCodecProvider;
+import com.alibaba.csp.sentinel.cluster.client.codec.registry.ResponseDataDecodeRegistry;
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.cluster.response.Response;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class NettyResponseDecoder extends ByteToMessageDecoder {
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
+ ResponseEntityDecoder responseDecoder = ClientEntityCodecProvider.getResponseEntityDecoder();
+ if (responseDecoder == null) {
+ // TODO: may need to throw exception?
+ RecordLog.warn("[NettyResponseDecoder] Cannot resolve the global response entity decoder, "
+ + "dropping the response");
+ return;
+ }
+
+ // TODO: handle decode error here.
+ Response response = responseDecoder.decode(in);
+ if (response != null) {
+ out.add(response);
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java
new file mode 100644
index 00000000..4ad9305d
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/RequestDataWriterRegistry.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ */
+public final class RequestDataWriterRegistry {
+
+ private static final Map> WRITER_MAP = new HashMap<>();
+
+ public static boolean addWriter(int type, EntityWriter writer) {
+ if (WRITER_MAP.containsKey(type)) {
+ return false;
+ }
+ WRITER_MAP.put(type, (EntityWriter)writer);
+ return true;
+ }
+
+ public static EntityWriter getWriter(int type) {
+ return WRITER_MAP.get(type);
+ }
+
+ public static boolean remove(int type) {
+ return WRITER_MAP.remove(type) != null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java
new file mode 100644
index 00000000..543fa774
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/registry/ResponseDataDecodeRegistry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.codec.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ResponseDataDecodeRegistry {
+
+ private static final Map> DECODER_MAP = new HashMap<>();
+
+ public static boolean addDecoder(int type, EntityDecoder decoder) {
+ if (DECODER_MAP.containsKey(type)) {
+ return false;
+ }
+ DECODER_MAP.put(type, decoder);
+ return true;
+ }
+
+ public static EntityDecoder getDecoder(int type) {
+ return (EntityDecoder)DECODER_MAP.get(type);
+ }
+
+ public static boolean removeDecoder(int type) {
+ return DECODER_MAP.remove(type) != null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
new file mode 100644
index 00000000..81a0edf0
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.config;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterClientConfig {
+
+ private String serverHost;
+ private int serverPort;
+
+ private int requestTimeout;
+ private int connectTimeout;
+
+ public String getServerHost() {
+ return serverHost;
+ }
+
+ public ClusterClientConfig setServerHost(String serverHost) {
+ this.serverHost = serverHost;
+ return this;
+ }
+
+ public int getServerPort() {
+ return serverPort;
+ }
+
+ public ClusterClientConfig setServerPort(int serverPort) {
+ this.serverPort = serverPort;
+ return this;
+ }
+
+ public int getRequestTimeout() {
+ return requestTimeout;
+ }
+
+ public ClusterClientConfig setRequestTimeout(int requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ return this;
+ }
+
+ public int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ public ClusterClientConfig setConnectTimeout(int connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ return this;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
new file mode 100644
index 00000000..94ade2d9
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.config;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+
+/**
+ * @author Eric Zhao
+ */
+public final class ClusterClientConfigManager {
+
+ private static volatile String serverIp;
+ private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT;
+ private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT;
+
+ private ClusterClientConfigManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
new file mode 100644
index 00000000..fc14cd35
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.handler;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.csp.sentinel.cluster.client.ClientConstants;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class TokenClientHandler extends ChannelInboundHandlerAdapter {
+
+ private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF);
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ currentState.set(ClientConstants.CLIENT_STATUS_STARTED);
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ System.out.println(String.format("[%s] Client message recv: %s", System.currentTimeMillis(), msg));
+ if (msg instanceof ClusterResponse) {
+ ClusterResponse> response = (ClusterResponse) msg;
+
+ TokenClientPromiseHolder.completePromise(response.getId(), response);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ super.exceptionCaught(ctx, cause);
+ cause.printStackTrace();
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ super.channelInactive(ctx);
+ }
+
+ @Override
+ public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+ currentState.set(ClientConstants.CLIENT_STATUS_OFF);
+ }
+
+ public int getCurrentState() {
+ return currentState.get();
+ }
+
+ public boolean hasStarted() {
+ return getCurrentState() == ClientConstants.CLIENT_STATUS_STARTED;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java
new file mode 100644
index 00000000..033d5900
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientPromiseHolder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.handler;
+
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+
+import io.netty.channel.ChannelPromise;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class TokenClientPromiseHolder {
+
+ private static final Map> PROMISE_MAP = new ConcurrentHashMap<>();
+
+ public static void putPromise(int xid, ChannelPromise promise) {
+ PROMISE_MAP.put(xid, new SimpleEntry(promise, null));
+ }
+
+ public static SimpleEntry getEntry(int xid) {
+ return PROMISE_MAP.get(xid);
+ }
+
+ public static void remove(int xid) {
+ PROMISE_MAP.remove(xid);
+ }
+
+ public static boolean completePromise(int xid, ClusterResponse response) {
+ if (!PROMISE_MAP.containsKey(xid)) {
+ return false;
+ }
+ ChannelPromise promise = PROMISE_MAP.get(xid).getKey();
+ if (promise.isDone() || promise.isCancelled()) {
+ return false;
+ }
+ PROMISE_MAP.get(xid).setValue(response);
+ promise.setSuccess();
+ return true;
+ }
+
+ private TokenClientPromiseHolder() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter
new file mode 100644
index 00000000..23698745
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityWriter
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.cluster.client.codec.DefaultRequestEntityWriter
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder
new file mode 100644
index 00000000..cd43e7e2
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityDecoder
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.cluster.client.codec.DefaultResponseEntityDecoder
\ No newline at end of file
From f83ea428ff2f72f485f18733e9c8239b6938e6d9 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Tue, 27 Nov 2018 13:07:20 +0800
Subject: [PATCH 06/20] Update token client interface and default
implementation
Signed-off-by: Eric Zhao
---
.../sentinel-cluster-client-default/pom.xml | 16 +++
.../client/DefaultClusterTokenClient.java | 88 ++++++++++++--
.../cluster/client/NettyTransportClient.java | 28 +++--
.../codec/DefaultRequestEntityWriter.java | 4 +-
.../codec/netty/NettyRequestEncoder.java | 1 +
.../client/config/ClusterClientConfig.java | 10 ++
.../config/ClusterClientConfigManager.java | 111 +++++++++++++++++-
.../client/config/ServerChangeObserver.java | 30 +++++
.../client/handler/TokenClientHandler.java | 8 +-
.../ModifyClusterClientConfigHandler.java | 42 +++++++
...ba.csp.sentinel.cluster.ClusterTokenClient | 1 +
...libaba.csp.sentinel.command.CommandHandler | 1 +
.../cluster/ClusterTransportClient.java | 21 ++++
.../sentinel/cluster/ClusterTokenClient.java | 2 +-
.../cluster/TokenServerDescriptor.java | 17 +--
.../alibaba/csp/sentinel/util/AssertUtil.java | 6 +
16 files changed, 345 insertions(+), 41 deletions(-)
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java
create mode 100644 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient
create mode 100755 sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler
diff --git a/sentinel-cluster/sentinel-cluster-client-default/pom.xml b/sentinel-cluster/sentinel-cluster-client-default/pom.xml
index 42b89d43..de189630 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/pom.xml
+++ b/sentinel-cluster/sentinel-cluster-client-default/pom.xml
@@ -17,6 +17,11 @@
com.alibaba.csp
sentinel-core
+
+ com.alibaba.csp
+ sentinel-transport-common
+ provided
+
com.alibaba.csp
sentinel-cluster-common-default
@@ -26,5 +31,16 @@
io.netty
netty-all
+
+
+ junit
+ junit
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
index 668f43bd..1d7bc646 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java
@@ -24,32 +24,92 @@ import com.alibaba.csp.sentinel.cluster.TokenResult;
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
import com.alibaba.csp.sentinel.cluster.TokenServerDescriptor;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
+import com.alibaba.csp.sentinel.cluster.client.config.ServerChangeObserver;
import com.alibaba.csp.sentinel.cluster.log.ClusterStatLogUtil;
import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.StringUtil;
/**
+ * Default implementation of {@link ClusterTokenClient}.
+ *
* @author Eric Zhao
* @since 1.4.0
*/
public class DefaultClusterTokenClient implements ClusterTokenClient {
private ClusterTransportClient transportClient;
+ private TokenServerDescriptor serverDescriptor;
public DefaultClusterTokenClient() {
- // TODO: load and create transport client here.
+ ClusterClientConfigManager.addServerChangeObserver(new ServerChangeObserver() {
+ @Override
+ public void onRemoteServerChange(ClusterClientConfig clusterClientConfig) {
+ changeServer(clusterClientConfig);
+ }
+ });
+ initNewConnection();
}
public DefaultClusterTokenClient(ClusterTransportClient transportClient) {
+ // TODO: only for test, remove this constructor.
this.transportClient = transportClient;
}
+ private boolean serverEqual(TokenServerDescriptor descriptor, ClusterClientConfig config) {
+ if (descriptor == null || config == null) {
+ return false;
+ }
+ return descriptor.getHost().equals(config.getServerHost()) && descriptor.getPort() == config.getServerPort();
+ }
+
+ private void initNewConnection() {
+ if (transportClient != null) {
+ return;
+ }
+ String host = ClusterClientConfigManager.getServerHost();
+ int port = ClusterClientConfigManager.getServerPort();
+ if (StringUtil.isBlank(host) || port <= 0) {
+ return;
+ }
+
+ try {
+ this.transportClient = new NettyTransportClient(host, port);
+ this.serverDescriptor = new TokenServerDescriptor(host, port);
+ transportClient.start();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ private void changeServer(/*@Valid*/ ClusterClientConfig config) {
+ if (serverEqual(serverDescriptor, config)) {
+ return;
+ }
+ try {
+ // TODO: what if the client is pending init?
+ if (transportClient != null && transportClient.isReady()) {
+ transportClient.stop();
+ }
+ // Replace with new, even if the new client is not ready.
+ this.transportClient = new NettyTransportClient(config);
+ this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort());
+ transportClient.start();
+ RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor);
+ } catch (Exception ex) {
+ RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex);
+ ex.printStackTrace();
+ }
+ }
+
@Override
public TokenServerDescriptor currentServer() {
- return new TokenServerDescriptor();
+ return serverDescriptor;
}
@Override
@@ -84,15 +144,11 @@ public class DefaultClusterTokenClient implements ClusterTokenClient {
}
}
- private boolean notValidRequest(Long id, int count) {
- return id == null || id <= 0 || count <= 0;
- }
-
- private TokenResult badRequest() {
- return new TokenResult(TokenResultStatus.BAD_REQUEST);
- }
-
private TokenResult sendTokenRequest(ClusterRequest request) throws Exception {
+ if (transportClient == null) {
+ RecordLog.warn("[DefaultClusterTokenClient] Client not created, please check your config for cluster client");
+ return clientFail();
+ }
ClusterResponse response = transportClient.sendRequest(request);
TokenResult result = new TokenResult(response.getStatus());
if (response.getData() != null) {
@@ -102,4 +158,16 @@ public class DefaultClusterTokenClient implements ClusterTokenClient {
}
return result;
}
+
+ private boolean notValidRequest(Long id, int count) {
+ return id == null || id <= 0 || count <= 0;
+ }
+
+ private TokenResult badRequest() {
+ return new TokenResult(TokenResultStatus.BAD_REQUEST);
+ }
+
+ private TokenResult clientFail() {
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
index 676f4d44..6fd21379 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/NettyTransportClient.java
@@ -23,6 +23,7 @@ import com.alibaba.csp.sentinel.cluster.ClusterTransportClient;
import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyRequestEncoder;
import com.alibaba.csp.sentinel.cluster.client.codec.netty.NettyResponseDecoder;
import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientHandler;
import com.alibaba.csp.sentinel.cluster.client.handler.TokenClientPromiseHolder;
import com.alibaba.csp.sentinel.cluster.exception.SentinelClusterException;
@@ -55,20 +56,25 @@ import io.netty.util.concurrent.GenericFutureListener;
*/
public class NettyTransportClient implements ClusterTransportClient {
- private ClusterClientConfig clientConfig;
- private String host;
- private int port;
+ private final String host;
+ private final int port;
private Channel channel;
private NioEventLoopGroup eventLoopGroup;
private TokenClientHandler clientHandler;
private AtomicInteger idGenerator = new AtomicInteger(0);
-
private AtomicInteger failConnectedTime = new AtomicInteger(0);
- public NettyTransportClient(ClusterClientConfig clientConfig, String host, int port) {
- this.clientConfig = clientConfig;
+ public NettyTransportClient(ClusterClientConfig clientConfig) {
+ AssertUtil.notNull(clientConfig, "client config cannot be null");
+ this.host = clientConfig.getServerHost();
+ this.port = clientConfig.getServerPort();
+ }
+
+ public NettyTransportClient(String host, int port) {
+ AssertUtil.assertNotBlank(host, "remote host cannot be blank");
+ AssertUtil.isTrue(port > 0, "port should be positive");
this.host = host;
this.port = port;
}
@@ -117,11 +123,13 @@ public class NettyTransportClient implements ClusterTransportClient {
});
}
- public void start() {
+ @Override
+ public void start() throws Exception {
connect(initClientBootstrap());
}
- public void stop() {
+ @Override
+ public void stop() throws Exception {
if (channel != null) {
channel.close();
channel = null;
@@ -138,6 +146,7 @@ public class NettyTransportClient implements ClusterTransportClient {
return request != null && request.getType() >= 0;
}
+ @Override
public boolean isReady() {
return channel != null && clientHandler != null && clientHandler.hasStarted();
}
@@ -159,8 +168,7 @@ public class NettyTransportClient implements ClusterTransportClient {
ChannelPromise promise = channel.newPromise();
TokenClientPromiseHolder.putPromise(xid, promise);
- // TODO: timeout
- if (!promise.await(20)) {
+ if (!promise.await(ClusterClientConfigManager.getRequestTimeout())) {
throw new SentinelClusterException(ClusterErrorMessages.REQUEST_TIME_OUT);
}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
index c4a3ce91..f3dad331 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java
@@ -37,8 +37,8 @@ public class DefaultRequestEntityWriter implements RequestEntityWriter, dropping the request", type);
+ RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{0}>,"
+ + " dropping the request", type);
return;
}
// Write head part of request.
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
index bc8b05cb..50a0476e 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/netty/NettyRequestEncoder.java
@@ -27,6 +27,7 @@ import io.netty.handler.codec.MessageToByteEncoder;
/**
* @author Eric Zhao
+ * @since 1.4.0
*/
public class NettyRequestEncoder extends MessageToByteEncoder {
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
index 81a0edf0..87f048e9 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfig.java
@@ -62,4 +62,14 @@ public class ClusterClientConfig {
this.connectTimeout = connectTimeout;
return this;
}
+
+ @Override
+ public String toString() {
+ return "ClusterClientConfig{" +
+ "serverHost='" + serverHost + '\'' +
+ ", serverPort=" + serverPort +
+ ", requestTimeout=" + requestTimeout +
+ ", connectTimeout=" + connectTimeout +
+ '}';
+ }
}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
index 94ade2d9..834bdcd7 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java
@@ -15,16 +15,125 @@
*/
package com.alibaba.csp.sentinel.cluster.client.config;
+import java.util.ArrayList;
+import java.util.List;
+
import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.alibaba.csp.sentinel.property.PropertyListener;
+import com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
/**
* @author Eric Zhao
+ * @since 1.4.0
*/
public final class ClusterClientConfigManager {
- private static volatile String serverIp;
+ /**
+ * Client config properties.
+ */
+ private static volatile String serverHost = null;
private static volatile int serverPort = ClusterConstants.DEFAULT_CLUSTER_SERVER_PORT;
private static volatile int requestTimeout = ClusterConstants.DEFAULT_REQUEST_TIMEOUT;
+ private static final PropertyListener PROPERTY_LISTENER = new ClientConfigPropertyListener();
+ private static SentinelProperty currentProperty = new DynamicSentinelProperty<>();
+
+ private static final List SERVER_CHANGE_OBSERVERS = new ArrayList<>();
+
+ static {
+ currentProperty.addListener(PROPERTY_LISTENER);
+ }
+
+ public static void register2Property(SentinelProperty property) {
+ synchronized (PROPERTY_LISTENER) {
+ RecordLog.info("[ClusterClientConfigManager] Registering new property to cluster client config manager");
+ currentProperty.removeListener(PROPERTY_LISTENER);
+ property.addListener(PROPERTY_LISTENER);
+ currentProperty = property;
+ }
+ }
+
+ public static void addServerChangeObserver(ServerChangeObserver observer) {
+ AssertUtil.notNull(observer, "observer cannot be null");
+ SERVER_CHANGE_OBSERVERS.add(observer);
+ }
+
+ /**
+ * Apply new {@link ClusterClientConfig}, while the former config will be replaced.
+ *
+ * @param config new config to apply
+ */
+ public static void applyNewConfig(ClusterClientConfig config) {
+ currentProperty.updateValue(config);
+ }
+
+ private static class ClientConfigPropertyListener implements PropertyListener {
+
+ @Override
+ public void configUpdate(ClusterClientConfig config) {
+ applyConfig(config);
+ }
+
+ @Override
+ public void configLoad(ClusterClientConfig config) {
+ if (config == null) {
+ RecordLog.warn("[ClusterClientConfigManager] Empty initial config");
+ return;
+ }
+ applyConfig(config);
+ }
+
+ private synchronized void applyConfig(ClusterClientConfig config) {
+ if (!isValidConfig(config)) {
+ RecordLog.warn(
+ "[ClusterClientConfigManager] Invalid cluster client config, ignoring: " + config);
+ return;
+ }
+ RecordLog.info("[ClusterClientConfigManager] Updating new config: " + config);
+ if (config.getRequestTimeout() != requestTimeout) {
+ requestTimeout = config.getRequestTimeout();
+ }
+ updateServer(config);
+ }
+ }
+
+ public static boolean isValidConfig(ClusterClientConfig config) {
+ return config != null && StringUtil.isNotBlank(config.getServerHost())
+ && config.getServerPort() > 0
+ && config.getRequestTimeout() > 0;
+ }
+
+ public static void updateServer(ClusterClientConfig config) {
+ String host = config.getServerHost();
+ int port = config.getServerPort();
+ AssertUtil.assertNotBlank(host, "token server host cannot be empty");
+ AssertUtil.isTrue(port > 0, "token server port should be valid (positive)");
+ if (serverPort == port && host.equals(serverHost)) {
+ return;
+ }
+ for (ServerChangeObserver observer : SERVER_CHANGE_OBSERVERS) {
+ observer.onRemoteServerChange(config);
+ }
+
+ serverHost = host;
+ serverPort = port;
+ }
+
+ public static String getServerHost() {
+ return serverHost;
+ }
+
+ public static int getServerPort() {
+ return serverPort;
+ }
+
+ public static int getRequestTimeout() {
+ return requestTimeout;
+ }
+
private ClusterClientConfigManager() {}
}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java
new file mode 100644
index 00000000..30908476
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ServerChangeObserver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.client.config;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ServerChangeObserver {
+
+ /**
+ * Callback on remote server address change.
+ *
+ * @param clusterClientConfig new cluster client config
+ */
+ void onRemoteServerChange(ClusterClientConfig clusterClientConfig);
+}
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
index fc14cd35..564b9bb5 100644
--- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java
@@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.alibaba.csp.sentinel.cluster.client.ClientConstants;
import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.log.RecordLog;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -34,6 +35,7 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
currentState.set(ClientConstants.CLIENT_STATUS_STARTED);
+ RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + ctx.channel().remoteAddress());
}
@Override
@@ -48,13 +50,13 @@ public class TokenClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- super.exceptionCaught(ctx, cause);
- cause.printStackTrace();
+ // TODO: should close the connection when an exception is raised.
+ RecordLog.warn("[TokenClientHandler] Client exception caught", cause);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- super.channelInactive(ctx);
+ RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: " + ctx.channel().remoteAddress());
}
@Override
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java
new file mode 100644
index 00000000..5cc5b4f8
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.command.handler;
+
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfig;
+import com.alibaba.csp.sentinel.cluster.client.config.ClusterClientConfigManager;
+import com.alibaba.csp.sentinel.command.CommandHandler;
+import com.alibaba.csp.sentinel.command.CommandRequest;
+import com.alibaba.csp.sentinel.command.CommandResponse;
+import com.alibaba.csp.sentinel.command.annotation.CommandMapping;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+@CommandMapping(name = "modifyClusterConfig")
+public class ModifyClusterClientConfigHandler implements CommandHandler {
+
+ @Override
+ public CommandResponse handle(CommandRequest request) {
+
+ // TODO: parse the new config;
+ ClusterClientConfig clusterClientConfig = null;
+ ClusterClientConfigManager.applyNewConfig(clusterClientConfig);
+
+ return CommandResponse.ofSuccess("ok");
+ }
+}
+
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient
new file mode 100644
index 00000000..3070db6a
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.ClusterTokenClient
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.cluster.client.DefaultClusterTokenClient
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler
new file mode 100755
index 00000000..a0af73b5
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.command.CommandHandler
@@ -0,0 +1 @@
+com.alibaba.csp.sentinel.command.handler.ModifyClusterClientConfigHandler
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
index 26af3c20..23ba9014 100644
--- a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
+++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTransportClient.java
@@ -26,6 +26,20 @@ import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
*/
public interface ClusterTransportClient {
+ /**
+ * Start the client.
+ *
+ * @throws Exception some error occurred (e.g. initialization failed)
+ */
+ void start() throws Exception;
+
+ /**
+ * Stop the client.
+ *
+ * @throws Exception some error occurred (e.g. shutdown failed)
+ */
+ void stop() throws Exception;
+
/**
* Send request to remote server and get response.
*
@@ -34,4 +48,11 @@ public interface ClusterTransportClient {
* @throws Exception some error occurs
*/
ClusterResponse sendRequest(ClusterRequest request) throws Exception;
+
+ /**
+ * Check whether the client has been started and ready for sending requests.
+ *
+ * @return true if the client is ready to send requests, otherwise false
+ */
+ boolean isReady();
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
index 6a230c07..c0243974 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterTokenClient.java
@@ -26,7 +26,7 @@ public interface ClusterTokenClient extends TokenService {
/**
* Get descriptor of current token server.
*
- * @return current token server
+ * @return current token server if connected, otherwise null
*/
TokenServerDescriptor currentServer();
}
\ No newline at end of file
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
index 98739179..98636346 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java
@@ -23,11 +23,10 @@ package com.alibaba.csp.sentinel.cluster;
*/
public class TokenServerDescriptor {
- private String host;
- private int port;
- private String type;
+ private final String host;
+ private final int port;
- public TokenServerDescriptor() {}
+ private String type = "default";
public TokenServerDescriptor(String host, int port) {
this.host = host;
@@ -38,20 +37,10 @@ public class TokenServerDescriptor {
return host;
}
- public TokenServerDescriptor setHost(String host) {
- this.host = host;
- return this;
- }
-
public int getPort() {
return port;
}
- public TokenServerDescriptor setPort(int port) {
- this.port = port;
- return this;
- }
-
public String getType() {
return type;
}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
index 46dcc11e..4c67b738 100644
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AssertUtil.java
@@ -28,6 +28,12 @@ public class AssertUtil {
}
}
+ public static void assertNotBlank(String string, String message) {
+ if (StringUtil.isBlank(string)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
public static void notNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
From b973aca1f59eed184df1162560e05a0c960a0487 Mon Sep 17 00:00:00 2001
From: Eric Zhao
Date: Tue, 27 Nov 2018 13:48:27 +0800
Subject: [PATCH 07/20] Add implementation for default token server module
Signed-off-by: Eric Zhao
---
.../cluster/flow/ClusterFlowChecker.java | 193 ++++++++++++++++++
.../cluster/flow/ClusterFlowRuleManager.java | 137 +++++++++++++
.../cluster/flow/ClusterParamFlowChecker.java | 82 ++++++++
.../flow/ClusterParamFlowRuleManager.java | 138 +++++++++++++
.../cluster/flow/DefaultTokenService.java | 77 +++++++
.../statistic/ClusterMetricStatistics.java | 59 ++++++
.../ClusterParamMetricStatistics.java | 59 ++++++
.../flow/statistic/data/ClusterFlowEvent.java | 42 ++++
.../statistic/data/ClusterMetricBucket.java | 49 +++++
.../flow/statistic/metric/ClusterMetric.java | 76 +++++++
.../metric/ClusterMetricLeapArray.java | 87 ++++++++
.../statistic/metric/ClusterParamMetric.java | 74 +++++++
.../metric/ClusterParameterLeapArray.java | 56 +++++
.../cluster/server/ClusterTokenServer.java | 29 +++
.../cluster/server/NettyTransportServer.java | 175 ++++++++++++++++
.../cluster/server/ServerConstants.java | 31 +++
.../cluster/server/TokenServiceProvider.java | 64 ++++++
.../codec/DefaultRequestEntityDecoder.java | 65 ++++++
.../codec/DefaultResponseEntityWriter.java | 53 +++++
.../codec/ServerEntityCodecProvider.java | 66 ++++++
.../codec/data/FlowRequestDataDecoder.java | 50 +++++
.../codec/data/FlowResponseDataWriter.java | 34 +++
.../data/ParamFlowRequestDataDecoder.java | 91 +++++++++
.../codec/netty/NettyRequestDecoder.java | 51 +++++
.../codec/netty/NettyResponseEncoder.java | 55 +++++
.../registry/RequestDataDecodeRegistry.java | 48 +++++
.../registry/ResponseDataWriterRegistry.java | 48 +++++
.../config/ClusterServerConfigManager.java | 36 ++++
.../cluster/server/connection/Connection.java | 37 ++++
.../server/connection/ConnectionGroup.java | 85 ++++++++
.../server/connection/ConnectionManager.java | 32 +++
.../server/connection/ConnectionPool.java | 149 ++++++++++++++
.../server/connection/NettyConnection.java | 85 ++++++++
.../connection/ScanIdleConnectionTask.java | 43 ++++
.../server/handler/TokenServerHandler.java | 84 ++++++++
.../processor/FlowRequestProcessor.java | 51 +++++
.../processor/ParamFlowRequestProcessor.java | 53 +++++
.../server/processor/RequestProcessor.java | 38 ++++
.../processor/RequestProcessorRegistry.java | 49 +++++
.../cluster/server/util/ClusterRuleUtil.java | 28 +++
....alibaba.csp.sentinel.cluster.TokenService | 1 +
...cluster.codec.request.RequestEntityDecoder | 1 +
...luster.codec.response.ResponseEntityWriter | 1 +
43 files changed, 2762 insertions(+)
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/FlowRequestProcessor.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/ParamFlowRequestProcessor.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessor.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/processor/RequestProcessorRegistry.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/util/ClusterRuleUtil.java
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.TokenService
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder
create mode 100644 sentinel-cluster/sentinel-cluster-server-default/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
new file mode 100644
index 00000000..836d338f
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowChecker.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow;
+
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
+import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.util.TimeUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterFlowChecker {
+
+ static TokenResult tryAcquireOrBorrowFromRefResource(FlowRule rule, int acquireCount, boolean prioritized) {
+ // 1. First try acquire its own count.
+
+ // TokenResult ownResult = acquireClusterToken(rule, acquireCount, prioritized);
+ ClusterMetric metric = ClusterMetricStatistics.getMetric(rule.getClusterConfig().getFlowId());
+ if (metric == null) {
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+
+ double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST);
+ double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.exceedCount;
+ double nextRemaining = globalThreshold - latestQps - acquireCount;
+
+ if (nextRemaining >= 0) {
+ // TODO: checking logic and metric operation should be separated.
+ metric.add(ClusterFlowEvent.PASS, acquireCount);
+ metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
+ if (prioritized) {
+ // Add prioritized pass.
+ metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);
+ }
+ // Remaining count is cut down to a smaller integer.
+ return new TokenResult(TokenResultStatus.OK)
+ .setRemaining((int) nextRemaining)
+ .setWaitInMs(0);
+ }
+
+ if (prioritized) {
+ double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);
+ if (occupyAvg <= ClusterServerConfigManager.maxOccupyRatio * globalThreshold) {
+ int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);
+ if (waitInMs > 0) {
+ return new TokenResult(TokenResultStatus.SHOULD_WAIT)
+ .setRemaining(0)
+ .setWaitInMs(waitInMs);
+ }
+ // Or else occupy failed, should be blocked.
+ }
+ }
+
+ // 2. If failed, try to borrow from reference resource.
+
+ // Assume it's valid as checked before.
+ if (!ClusterServerConfigManager.borrowRefEnabled) {
+ return new TokenResult(TokenResultStatus.NOT_AVAILABLE);
+ }
+ Long refFlowId = rule.getClusterConfig().getRefFlowId();
+ FlowRule refFlowRule = ClusterFlowRuleManager.getFlowRuleById(refFlowId);
+ if (refFlowRule == null) {
+ return new TokenResult(TokenResultStatus.NO_REF_RULE_EXISTS);
+ }
+ // TODO: check here
+
+ ClusterMetric refMetric = ClusterMetricStatistics.getMetric(refFlowId);
+ if (refMetric == null) {
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+ double refOrders = refMetric.getAvg(ClusterFlowEvent.PASS);
+ double refQps = refMetric.getAvg(ClusterFlowEvent.PASS_REQUEST);
+
+ double splitRatio = refQps > 0 ? refOrders / refQps : 1;
+
+ double selfGlobalThreshold = ClusterServerConfigManager.exceedCount * calcGlobalThreshold(rule);
+ double refGlobalThreshold = ClusterServerConfigManager.exceedCount * calcGlobalThreshold(refFlowRule);
+
+ long currentTime = TimeUtil.currentTimeMillis();
+ long latestRefTime = 0 /*refFlowRule.clusterQps.getStableWindowStartTime()*/;
+ int sampleCount = 10;
+
+ if (currentTime > latestRefTime
+ && (refOrders / refGlobalThreshold + 1.0d / sampleCount >= ((double)(currentTime - latestRefTime)) / 1000)
+ || refOrders == refGlobalThreshold) {
+ return blockedResult();
+ }
+
+ // double latestQps = metric.getAvg(ClusterFlowEvent.PASS);
+ double refRatio = rule.getClusterConfig().getRefRatio();
+
+ if (refOrders / splitRatio + (acquireCount + latestQps) * refRatio
+ <= refGlobalThreshold / splitRatio + selfGlobalThreshold * refRatio) {
+ metric.add(ClusterFlowEvent.PASS, acquireCount);
+ metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
+
+ return new TokenResult(TokenResultStatus.OK);
+ }
+
+ // TODO: log here?
+ metric.add(ClusterFlowEvent.BLOCK, acquireCount);
+
+ return blockedResult();
+ }
+
+ private static double calcGlobalThreshold(FlowRule rule) {
+ double count = rule.getCount();
+ switch (rule.getClusterConfig().getThresholdType()) {
+ case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
+ return count;
+ case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
+ default:
+ // TODO: get real connected count grouped.
+ int connectedCount = 1;
+ return count * connectedCount;
+ }
+ }
+
+ static TokenResult acquireClusterToken(/*@Valid*/ FlowRule rule, int acquireCount, boolean prioritized) {
+ ClusterMetric metric = ClusterMetricStatistics.getMetric(rule.getClusterConfig().getFlowId());
+ if (metric == null) {
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+
+ double latestQps = metric.getAvg(ClusterFlowEvent.PASS_REQUEST);
+ double globalThreshold = calcGlobalThreshold(rule) * ClusterServerConfigManager.exceedCount;
+ double nextRemaining = globalThreshold - latestQps - acquireCount;
+
+ if (nextRemaining >= 0) {
+ // TODO: checking logic and metric operation should be separated.
+ metric.add(ClusterFlowEvent.PASS, acquireCount);
+ metric.add(ClusterFlowEvent.PASS_REQUEST, 1);
+ if (prioritized) {
+ // Add prioritized pass.
+ metric.add(ClusterFlowEvent.OCCUPIED_PASS, acquireCount);
+ }
+ // Remaining count is cut down to a smaller integer.
+ return new TokenResult(TokenResultStatus.OK)
+ .setRemaining((int) nextRemaining)
+ .setWaitInMs(0);
+ } else {
+ if (prioritized) {
+ double occupyAvg = metric.getAvg(ClusterFlowEvent.WAITING);
+ if (occupyAvg <= ClusterServerConfigManager.maxOccupyRatio * globalThreshold) {
+ int waitInMs = metric.tryOccupyNext(ClusterFlowEvent.PASS, acquireCount, globalThreshold);
+ if (waitInMs > 0) {
+ return new TokenResult(TokenResultStatus.SHOULD_WAIT)
+ .setRemaining(0)
+ .setWaitInMs(waitInMs);
+ }
+ // Or else occupy failed, should be blocked.
+ }
+ }
+ // Blocked.
+ metric.add(ClusterFlowEvent.BLOCK, acquireCount);
+ metric.add(ClusterFlowEvent.BLOCK_REQUEST, 1);
+ if (prioritized) {
+ // Add prioritized block.
+ metric.add(ClusterFlowEvent.OCCUPIED_BLOCK, acquireCount);
+ }
+
+ return blockedResult();
+ }
+ }
+
+ private static TokenResult blockedResult() {
+ return new TokenResult(TokenResultStatus.BLOCKED)
+ .setRemaining(0)
+ .setWaitInMs(0);
+ }
+
+ private ClusterFlowChecker() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java
new file mode 100644
index 00000000..2f45ec63
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterFlowRuleManager.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterMetricStatistics;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
+import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.alibaba.csp.sentinel.property.PropertyListener;
+import com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterFlowRuleManager {
+
+ private static final Map FLOW_RULES = new ConcurrentHashMap<>();
+
+ private static final PropertyListener> PROPERTY_LISTENER = new FlowRulePropertyListener();
+ private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>();
+
+ static {
+ currentProperty.addListener(PROPERTY_LISTENER);
+ }
+
+ /**
+ * Listen to the {@link SentinelProperty} for {@link FlowRule}s.
+ * The property is the source of cluster {@link FlowRule}s.
+ *
+ * @param property the property to listen.
+ */
+ public static void register2Property(SentinelProperty> property) {
+ synchronized (PROPERTY_LISTENER) {
+ RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager");
+ currentProperty.removeListener(PROPERTY_LISTENER);
+ property.addListener(PROPERTY_LISTENER);
+ currentProperty = property;
+ }
+ }
+
+ public static FlowRule getFlowRuleById(Long id) {
+ if (!ClusterRuleUtil.validId(id)) {
+ return null;
+ }
+ return FLOW_RULES.get(id);
+ }
+
+ private static Map buildClusterFlowRuleMap(List list) {
+ Map ruleMap = new ConcurrentHashMap<>();
+ if (list == null || list.isEmpty()) {
+ return ruleMap;
+ }
+
+ for (FlowRule rule : list) {
+ if (!rule.isClusterMode()) {
+ continue;
+ }
+ if (!FlowRuleUtil.isValidRule(rule)) {
+ RecordLog.warn(
+ "[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule);
+ continue;
+ }
+ if (StringUtil.isBlank(rule.getLimitApp())) {
+ rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ // Flow id should not be null after filtered.
+ Long flowId = rule.getClusterConfig().getFlowId();
+ if (flowId == null) {
+ continue;
+ }
+ ruleMap.put(flowId, rule);
+
+ // Prepare cluster metric from valid flow ID.
+ ClusterMetricStatistics.putMetricIfAbsent(flowId, new ClusterMetric(100, 1));
+ }
+
+ // Cleanup unused cluster metrics.
+ Set previousSet = FLOW_RULES.keySet();
+ for (Long id : previousSet) {
+ if (!ruleMap.containsKey(id)) {
+ ClusterMetricStatistics.removeMetric(id);
+ }
+ }
+
+ return ruleMap;
+ }
+
+ private static final class FlowRulePropertyListener implements PropertyListener> {
+
+ @Override
+ public void configUpdate(List conf) {
+ Map rules = buildClusterFlowRuleMap(conf);
+ if (rules != null) {
+ FLOW_RULES.clear();
+ FLOW_RULES.putAll(rules);
+ }
+ RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received: " + FLOW_RULES);
+ }
+
+ @Override
+ public void configLoad(List conf) {
+ Map rules = buildClusterFlowRuleMap(conf);
+ if (rules != null) {
+ FLOW_RULES.clear();
+ FLOW_RULES.putAll(rules);
+ }
+ RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded: " + FLOW_RULES);
+ }
+ }
+
+ private ClusterFlowRuleManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java
new file mode 100644
index 00000000..04d09668
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowChecker.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow;
+
+import java.util.Collection;
+
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+
+/**
+ * @author Eric Zhao
+ */
+public final class ClusterParamFlowChecker {
+
+ static TokenResult acquireClusterToken(ParamFlowRule rule, int count, Collection values) {
+ ClusterParamMetric metric = ClusterParamMetricStatistics.getMetric(rule.getClusterConfig().getFlowId());
+ if (metric == null) {
+ // Unexpected state, return FAIL.
+ return new TokenResult(TokenResultStatus.FAIL);
+ }
+ boolean hasPassed = true;
+ Object blockObject = null;
+ for (Object value : values) {
+ // TODO: origin is int * int, but current double!
+ double curCount = metric.getAvg(value);
+
+ double threshold = calcGlobalThreshold(rule);
+ if (++curCount > threshold) {
+ hasPassed = false;
+ blockObject = value;
+ break;
+ }
+ }
+
+ if (hasPassed) {
+ for (Object value : values) {
+ metric.addValue(value, count);
+ }
+ } else {
+ // TODO: log here?
+ }
+
+ return hasPassed ? newRawResponse(TokenResultStatus.OK): newRawResponse(TokenResultStatus.BLOCKED);
+ }
+
+ private static TokenResult newRawResponse(int status) {
+ return new TokenResult(status)
+ .setRemaining(0)
+ .setWaitInMs(0);
+ }
+
+ private static double calcGlobalThreshold(ParamFlowRule rule) {
+ double count = rule.getCount();
+ switch (rule.getClusterConfig().getThresholdType()) {
+ case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL:
+ return count;
+ case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL:
+ default:
+ int connectedCount = 1; // TODO: get real connected count grouped.
+ return count * connectedCount;
+ }
+ }
+
+ private ClusterParamFlowChecker() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java
new file mode 100644
index 00000000..c0a4278f
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ClusterParamFlowRuleManager.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.ClusterParamMetricStatistics;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
+import com.alibaba.csp.sentinel.cluster.server.util.ClusterRuleUtil;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.alibaba.csp.sentinel.property.PropertyListener;
+import com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleUtil;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterParamFlowRuleManager {
+
+ private static final Map PARAM_RULES = new ConcurrentHashMap<>();
+
+ private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener();
+ private static SentinelProperty> currentProperty
+ = new DynamicSentinelProperty>();
+
+ static {
+ currentProperty.addListener(PROPERTY_LISTENER);
+ }
+
+ /**
+ * Listen to the {@link SentinelProperty} for {@link ParamFlowRule}s.
+ * The property is the source of {@link ParamFlowRule}s.
+ *
+ * @param property the property to listen
+ */
+ public static void register2Property(SentinelProperty> property) {
+ synchronized (PROPERTY_LISTENER) {
+ currentProperty.removeListener(PROPERTY_LISTENER);
+ property.addListener(PROPERTY_LISTENER);
+ currentProperty = property;
+ RecordLog.info("[ClusterParamFlowRuleManager] New property has been registered to cluster param rule manager");
+ }
+ }
+
+ public static ParamFlowRule getParamFlowRuleById(Long id) {
+ if (!ClusterRuleUtil.validId(id)) {
+ return null;
+ }
+ return PARAM_RULES.get(id);
+ }
+
+ static class RulePropertyListener implements PropertyListener> {
+
+ @Override
+ public void configUpdate(List conf) {
+ Map rules = buildClusterRuleMap(conf);
+ if (rules != null) {
+ PARAM_RULES.clear();
+ PARAM_RULES.putAll(rules);
+ }
+ RecordLog.info("[ClusterFlowRuleManager] Cluster param flow rules received: " + PARAM_RULES);
+ }
+
+ @Override
+ public void configLoad(List conf) {
+ Map rules = buildClusterRuleMap(conf);
+ if (rules != null) {
+ PARAM_RULES.clear();
+ PARAM_RULES.putAll(rules);
+ }
+ RecordLog.info("[ClusterFlowRuleManager] Cluster param flow rules received: " + PARAM_RULES);
+ }
+ }
+
+ private static Map buildClusterRuleMap(List list) {
+ Map ruleMap = new ConcurrentHashMap<>();
+ if (list == null || list.isEmpty()) {
+ return ruleMap;
+ }
+
+ for (ParamFlowRule rule : list) {
+ if (!rule.isClusterMode()) {
+ continue;
+ }
+ if (!ParamFlowRuleUtil.isValidRule(rule)) {
+ RecordLog.warn(
+ "[ClusterParamFlowRuleManager] Ignoring invalid param flow rule when loading new flow rules: " + rule);
+ continue;
+ }
+ if (StringUtil.isBlank(rule.getLimitApp())) {
+ rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ // Flow id should not be null after filtered.
+ Long flowId = rule.getClusterConfig().getFlowId();
+ if (flowId == null) {
+ continue;
+ }
+ ruleMap.put(flowId, rule);
+
+ // Prepare cluster metric from valid flow ID.
+ ClusterParamMetricStatistics.putMetricIfAbsent(flowId, new ClusterParamMetric(100, 1));
+ }
+
+ // Cleanup unused cluster metrics.
+ Set previousSet = PARAM_RULES.keySet();
+ for (Long id : previousSet) {
+ if (!ruleMap.containsKey(id)) {
+ ClusterParamMetricStatistics.removeMetric(id);
+ }
+ }
+
+ return ruleMap;
+ }
+
+ private ClusterParamFlowRuleManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java
new file mode 100644
index 00000000..f12a6d64
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow;
+
+import java.util.Collection;
+
+import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.alibaba.csp.sentinel.cluster.TokenService;
+import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+
+/**
+ * Default implementation for cluster {@link TokenService}.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultTokenService implements TokenService {
+
+ @Override
+ public TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized) {
+ if (notValidRequest(ruleId, acquireCount)) {
+ return badRequest();
+ }
+ // The rule should be valid.
+ FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
+ if (rule == null) {
+ return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
+ }
+ if (isUsingReference(rule)) {
+ return ClusterFlowChecker.tryAcquireOrBorrowFromRefResource(rule, acquireCount, prioritized);
+ }
+
+ return ClusterFlowChecker.acquireClusterToken(rule, acquireCount, prioritized);
+ }
+
+ private boolean isUsingReference(FlowRule rule) {
+ return rule.getClusterConfig().getStrategy() == ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_REF;
+ }
+
+ @Override
+ public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params) {
+ if (notValidRequest(ruleId, acquireCount) || params == null || params.isEmpty()) {
+ return badRequest();
+ }
+ // The rule should be valid.
+ ParamFlowRule rule = ClusterParamFlowRuleManager.getParamFlowRuleById(ruleId);
+ if (rule == null) {
+ return new TokenResult(TokenResultStatus.NO_RULE_EXISTS);
+ }
+
+ return ClusterParamFlowChecker.acquireClusterToken(rule, acquireCount, params);
+ }
+
+ private boolean notValidRequest(Long id, int count) {
+ return id == null || id <= 0 || count <= 0;
+ }
+
+ private TokenResult badRequest() {
+ return new TokenResult(TokenResultStatus.BAD_REQUEST);
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java
new file mode 100644
index 00000000..155cc32d
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterMetricStatistics.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterMetric;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterMetricStatistics {
+
+ private static final Map METRIC_MAP = new ConcurrentHashMap<>();
+
+ public static void clear() {
+ METRIC_MAP.clear();
+ }
+
+ public static void putMetric(long id, ClusterMetric metric) {
+ AssertUtil.notNull(metric, "Cluster metric cannot be null");
+ METRIC_MAP.put(id, metric);
+ }
+
+ public static boolean putMetricIfAbsent(long id, ClusterMetric metric) {
+ AssertUtil.notNull(metric, "Cluster metric cannot be null");
+ if (METRIC_MAP.containsKey(id)) {
+ return false;
+ }
+ METRIC_MAP.put(id, metric);
+ return true;
+ }
+
+ public static void removeMetric(long id) {
+ METRIC_MAP.remove(id);
+ }
+
+ public static ClusterMetric getMetric(long id) {
+ return METRIC_MAP.get(id);
+ }
+
+ private ClusterMetricStatistics() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java
new file mode 100644
index 00000000..73445cda
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/ClusterParamMetricStatistics.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.metric.ClusterParamMetric;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ClusterParamMetricStatistics {
+
+ private static final Map METRIC_MAP = new ConcurrentHashMap<>();
+
+ public static void clear() {
+ METRIC_MAP.clear();
+ }
+
+ public static void putMetric(long id, ClusterParamMetric metric) {
+ AssertUtil.notNull(metric, "metric cannot be null");
+ METRIC_MAP.put(id, metric);
+ }
+
+ public static boolean putMetricIfAbsent(long id, ClusterParamMetric metric) {
+ AssertUtil.notNull(metric, "metric cannot be null");
+ if (METRIC_MAP.containsKey(id)) {
+ return false;
+ }
+ METRIC_MAP.put(id, metric);
+ return true;
+ }
+
+ public static void removeMetric(long id) {
+ METRIC_MAP.remove(id);
+ }
+
+ public static ClusterParamMetric getMetric(long id) {
+ return METRIC_MAP.get(id);
+ }
+
+ private ClusterParamMetricStatistics() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java
new file mode 100644
index 00000000..3f674ca9
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterFlowEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.data;
+
+/**
+ * @author Eric Zhao
+ */
+public enum ClusterFlowEvent {
+
+ /**
+ * Normal pass.
+ */
+ PASS,
+ /**
+ * Normal block.
+ */
+ BLOCK,
+ /**
+ * Token request (from client) passed.
+ */
+ PASS_REQUEST,
+ /**
+ * Token request (from client) blocked.
+ */
+ BLOCK_REQUEST,
+ OCCUPIED_PASS,
+ OCCUPIED_BLOCK,
+ WAITING
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java
new file mode 100644
index 00000000..6f1eee13
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/data/ClusterMetricBucket.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.data;
+
+import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
+
+/**
+ * @author Eric Zhao
+ */
+public class ClusterMetricBucket {
+
+ private final LongAdder[] counters;
+
+ public ClusterMetricBucket() {
+ ClusterFlowEvent[] events = ClusterFlowEvent.values();
+ this.counters = new LongAdder[events.length];
+ for (ClusterFlowEvent event : events) {
+ counters[event.ordinal()] = new LongAdder();
+ }
+ }
+
+ public void reset() {
+ for (ClusterFlowEvent event : ClusterFlowEvent.values()) {
+ counters[event.ordinal()].reset();
+ }
+ }
+
+ public long get(ClusterFlowEvent event) {
+ return counters[event.ordinal()].sum();
+ }
+
+ public ClusterMetricBucket add(ClusterFlowEvent event, long count) {
+ counters[event.ordinal()].add(count);
+ return this;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java
new file mode 100644
index 00000000..ba992759
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetric.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.metric;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterMetric {
+
+ private final ClusterMetricLeapArray metric;
+
+ public ClusterMetric(int windowLengthInMs, int intervalInSec) {
+ this.metric = new ClusterMetricLeapArray(windowLengthInMs, intervalInSec);
+ }
+
+ public void add(ClusterFlowEvent event, long count) {
+ metric.currentWindow().value().add(event, count);
+ }
+
+ public long getCurrentCount(ClusterFlowEvent event) {
+ return metric.currentWindow().value().get(event);
+ }
+
+ public long getSum(ClusterFlowEvent event) {
+ metric.currentWindow();
+ long sum = 0;
+
+ List buckets = metric.values();
+ for (ClusterMetricBucket bucket : buckets) {
+ sum += bucket.get(event);
+ }
+ return sum;
+ }
+
+ public double getAvg(ClusterFlowEvent event) {
+ return getSum(event) / metric.getIntervalInSecond();
+ }
+
+ /**
+ *
+ * @return time to wait for next bucket (in ms); 0 if cannot occupy next buckets
+ */
+ public int tryOccupyNext(ClusterFlowEvent event, int acquireCount, double threshold) {
+ double latestQps = getAvg(ClusterFlowEvent.PASS);
+ if (!canOccupy(event, acquireCount, latestQps, threshold)) {
+ return 0;
+ }
+ metric.addOccupyPass(acquireCount);
+ add(ClusterFlowEvent.WAITING, acquireCount);
+ return 1000 / metric.getSampleCount();
+ }
+
+ private boolean canOccupy(ClusterFlowEvent event, int acquireCount, double latestQps, double threshold) {
+ // TODO
+ return metric.getOccupiedCount(event) + latestQps + acquireCount /*- xxx*/ <= threshold;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java
new file mode 100644
index 00000000..86c02d07
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricLeapArray.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.metric;
+
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent;
+import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterMetricBucket;
+import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
+import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterMetricLeapArray extends LeapArray {
+
+ private final LongAdder[] occupyCounter;
+ private boolean hasOccupied = false;
+
+ /**
+ * The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs.
+ *
+ * @param windowLengthInMs a single window bucket's time length in milliseconds.
+ * @param intervalInSec the total time span of this {@link LeapArray} in seconds.
+ */
+ public ClusterMetricLeapArray(int windowLengthInMs, int intervalInSec) {
+ super(windowLengthInMs, intervalInSec);
+ ClusterFlowEvent[] events = ClusterFlowEvent.values();
+ this.occupyCounter = new LongAdder[events.length];
+ for (ClusterFlowEvent event : events) {
+ occupyCounter[event.ordinal()] = new LongAdder();
+ }
+ }
+
+ @Override
+ public ClusterMetricBucket newEmptyBucket() {
+ return new ClusterMetricBucket();
+ }
+
+ @Override
+ protected WindowWrap resetWindowTo(WindowWrap w, long startTime) {
+ w.resetTo(startTime);
+ w.value().reset();
+ transferOccupyToBucket(w.value());
+ return w;
+ }
+
+ private void transferOccupyToBucket(/*@Valid*/ ClusterMetricBucket bucket) {
+ if (hasOccupied) {
+ transferOccupiedCount(bucket, ClusterFlowEvent.PASS, ClusterFlowEvent.OCCUPIED_PASS);
+ transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS);
+ transferOccupiedThenReset(bucket, ClusterFlowEvent.PASS_REQUEST);
+ hasOccupied = false;
+ }
+ }
+
+ private void transferOccupiedCount(ClusterMetricBucket bucket, ClusterFlowEvent source, ClusterFlowEvent target) {
+ bucket.add(target, occupyCounter[source.ordinal()].sum());
+ }
+
+ private void transferOccupiedThenReset(ClusterMetricBucket bucket, ClusterFlowEvent event) {
+ bucket.add(event, occupyCounter[event.ordinal()].sumThenReset());
+ }
+
+ public void addOccupyPass(int count) {
+ occupyCounter[ClusterFlowEvent.PASS.ordinal()].add(count);
+ occupyCounter[ClusterFlowEvent.PASS_REQUEST.ordinal()].add(1);
+ this.hasOccupied = true;
+ }
+
+ public long getOccupiedCount(ClusterFlowEvent event) {
+ return occupyCounter[event.ordinal()].sum();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java
new file mode 100644
index 00000000..9dd52c45
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.metric;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder;
+import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
+
+/**
+ * @author Eric Zhao
+ */
+public class ClusterParamMetric {
+
+ private final ClusterParameterLeapArray metric;
+
+ public ClusterParamMetric(int windowLengthInMs, int intervalInSec) {
+ this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec);
+ }
+
+ public ClusterParamMetric(int windowLengthInMs, int intervalInSec, int maxCapacity) {
+ this.metric = new ClusterParameterLeapArray<>(windowLengthInMs, intervalInSec, maxCapacity);
+ }
+
+ public long getSum(Object value) {
+ if (value == null) {
+ return 0;
+ }
+
+ metric.currentWindow();
+ long sum = 0;
+
+ List> buckets = metric.values();
+ for (CacheMap bucket : buckets) {
+ sum += getCount(bucket.get(value));
+ }
+ return sum;
+ }
+
+ private long getCount(/*@Nullable*/ LongAdder adder) {
+ return adder == null ? 0 : adder.sum();
+ }
+
+ public void addValue(Object value, int count) {
+ if (value == null) {
+ return;
+ }
+ CacheMap data = metric.currentWindow().value();
+ LongAdder newCounter = new LongAdder();
+ LongAdder currentCounter = data.putIfAbsent(value, newCounter);
+ if (currentCounter != null) {
+ currentCounter.add(count);
+ } else {
+ newCounter.add(count);
+ }
+ }
+
+ public double getAvg(Object value) {
+ return getSum(value) / metric.getIntervalInSecond();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java
new file mode 100644
index 00000000..d11c64c6
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParameterLeapArray.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.flow.statistic.metric;
+
+import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap;
+import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * @author Eric Zhao
+ * @param counter type
+ * @since 1.4.0
+ */
+public class ClusterParameterLeapArray extends LeapArray> {
+
+ private final int maxCapacity;
+
+ public ClusterParameterLeapArray(int windowLengthInMs, int intervalInSec) {
+ this(windowLengthInMs, intervalInSec, DEFAULT_CLUSTER_MAX_CAPACITY);
+ }
+
+ public ClusterParameterLeapArray(int windowLengthInMs, int intervalInSec, int maxCapacity) {
+ super(windowLengthInMs, intervalInSec);
+ AssertUtil.isTrue(maxCapacity > 0, "maxCapacity of LRU map should be positive");
+ this.maxCapacity = maxCapacity;
+ }
+
+ @Override
+ public CacheMap newEmptyBucket() {
+ return new ConcurrentLinkedHashMapWrapper<>(maxCapacity);
+ }
+
+ @Override
+ protected WindowWrap> resetWindowTo(WindowWrap> w,
+ long startTime) {
+ w.value().clear();
+ return w;
+ }
+
+ public static final int DEFAULT_CLUSTER_MAX_CAPACITY = 4000;
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java
new file mode 100644
index 00000000..8d17612c
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server;
+
+/**
+ * Token server interface for distributed flow control.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public interface ClusterTokenServer {
+
+ void start() throws Exception;
+
+ void stop() throws Exception;
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java
new file mode 100644
index 00000000..5e3025b9
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/NettyTransportServer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyRequestDecoder;
+import com.alibaba.csp.sentinel.cluster.server.codec.netty.NettyResponseEncoder;
+import com.alibaba.csp.sentinel.cluster.server.connection.Connection;
+import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionPool;
+import com.alibaba.csp.sentinel.cluster.server.handler.TokenServerHandler;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.logging.LogLevel;
+import io.netty.handler.logging.LoggingHandler;
+import io.netty.util.concurrent.GenericFutureListener;
+import io.netty.util.internal.SystemPropertyUtil;
+
+import static com.alibaba.csp.sentinel.cluster.server.ServerConstants.*;
+
+/**
+ * @author Eric Zhao
+ */
+public class NettyTransportServer implements ClusterTokenServer {
+
+ private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
+ "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
+
+ private final int port = 11111;
+
+ private NioEventLoopGroup bossGroup;
+ private NioEventLoopGroup workerGroup;
+
+ private final ConnectionPool connectionPool = new ConnectionPool();
+
+ private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF);
+ private final AtomicInteger failedTimes = new AtomicInteger(0);
+
+ @Override
+ public void start() {
+ if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) {
+ return;
+ }
+
+ ServerBootstrap b = new ServerBootstrap();
+ this.bossGroup = new NioEventLoopGroup(1);
+ this.workerGroup = new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS);
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .option(ChannelOption.SO_BACKLOG, 128)
+ .handler(new LoggingHandler(LogLevel.INFO))
+ .childHandler(new ChannelInitializer() {
+ @Override
+ public void initChannel(SocketChannel ch) throws Exception {
+ ChannelPipeline p = ch.pipeline();
+ p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));
+ p.addLast(new NettyRequestDecoder());
+ p.addLast(new LengthFieldPrepender(2));
+ p.addLast(new NettyResponseEncoder());
+ p.addLast(new TokenServerHandler(connectionPool));
+ }
+ })
+ .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
+ .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
+ .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
+ .childOption(ChannelOption.SO_TIMEOUT, 10)
+ .childOption(ChannelOption.TCP_NODELAY, true)
+ .childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
+ b.bind(Integer.valueOf(port)).addListener(new GenericFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) {
+ if (future.cause() != null) {
+ RecordLog.info("Token server start failed", future.cause());
+ currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF);
+
+ //try {
+ // Thread.sleep((failStartTimes.get() + 1) * 1000);
+ // start();
+ //} catch (Throwable e) {
+ // RecordLog.info("Fail to start token server:", e);
+ //}
+ } else {
+ RecordLog.info("Token server start success");
+ currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED);
+ //failStartTimes.set(0);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void stop() {
+ // If still initializing, wait for ready.
+ while (currentState.get() == SERVER_STATUS_STARTING) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) {
+ try {
+ bossGroup.shutdownGracefully();
+ workerGroup.shutdownGracefully();
+ connectionPool.shutdownAll();
+
+ failedTimes.set(0);
+
+ RecordLog.info("Token server stopped");
+ } catch (Exception ex) {
+ RecordLog.warn("Failed to stop token server", ex);
+ }
+ }
+ }
+
+ public void refreshRunningServer() {
+ connectionPool.refreshIdleTask();
+ }
+
+ public void closeConnection(String clientIp, int clientPort) throws Exception {
+ Connection connection = connectionPool.getConnection(clientIp, clientPort);
+ connection.close();
+ }
+
+ public void closeAll() throws Exception {
+ List connections = connectionPool.listAllConnection();
+ for (Connection connection : connections) {
+ connection.close();
+ }
+ }
+
+ public List listAllClient() {
+ List clients = new ArrayList();
+ List connections = connectionPool.listAllConnection();
+ for (Connection conn : connections) {
+ clients.add(conn.getConnectionKey());
+ }
+ return clients;
+ }
+
+ public int getCurrentState() {
+ return currentState.get();
+ }
+
+ public int clientCount() {
+ return connectionPool.count();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java
new file mode 100644
index 00000000..6569cef8
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/ServerConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ServerConstants {
+
+ public static final int SERVER_STATUS_OFF = 0;
+ public static final int SERVER_STATUS_STARTING = 1;
+ public static final int SERVER_STATUS_STARTED = 2;
+
+ public static final String DEFAULT_NAMESPACE = "default";
+
+ private ServerConstants() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
new file mode 100644
index 00000000..8e3c2706
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/TokenServiceProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import com.alibaba.csp.sentinel.cluster.TokenService;
+import com.alibaba.csp.sentinel.cluster.flow.DefaultTokenService;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class TokenServiceProvider {
+
+ private static TokenService service = null;
+
+ private static final ServiceLoader LOADER = ServiceLoader.load(TokenService.class);
+
+ static {
+ resolveTokenServiceSpi();
+ }
+
+ public static TokenService getService() {
+ return service;
+ }
+
+ private static void resolveTokenServiceSpi() {
+ boolean hasOther = false;
+ List list = new ArrayList();
+ for (TokenService service : LOADER) {
+ if (service.getClass() != DefaultTokenService.class) {
+ hasOther = true;
+ list.add(service);
+ }
+ }
+
+ if (hasOther) {
+ service = list.get(0);
+ } else {
+ // No custom token service, using default.
+ service = new DefaultTokenService();
+ }
+
+ RecordLog.info("[TokenServiceProvider] Global token service resolved: "
+ + service.getClass().getCanonicalName());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java
new file mode 100644
index 00000000..9012ae3f
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultRequestEntityDecoder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
+import com.alibaba.csp.sentinel.cluster.request.ClusterRequest;
+import com.alibaba.csp.sentinel.cluster.server.codec.registry.RequestDataDecodeRegistry;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * Default entity decoder for any {@link ClusterRequest} entity.
+ *
+ * Decode format:
+ *
+ * +--------+---------+---------+
+ * | xid(4) | type(1) | data... |
+ * +--------+---------+---------+
+ *
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultRequestEntityDecoder implements RequestEntityDecoder {
+
+ @Override
+ public ClusterRequest decode(ByteBuf source) {
+ if (source.readableBytes() >= 5) {
+ int xid = source.readInt();
+ int type = source.readByte();
+
+ EntityDecoder dataDecoder = RequestDataDecodeRegistry.getDecoder(type);
+ if (dataDecoder == null) {
+ RecordLog.warn("Unknown type of request data decoder: {0}", type);
+ return null;
+ }
+
+ Object data;
+ if (source.readableBytes() == 0) {
+ data = null;
+ } else {
+ // TODO: handle decode error here.
+ data = dataDecoder.decode(source);
+ }
+
+ return new ClusterRequest<>(xid, type, data);
+ }
+ return null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java
new file mode 100644
index 00000000..e56faa09
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.cluster.response.Response;
+import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class DefaultResponseEntityWriter implements ResponseEntityWriter {
+
+ @Override
+ public void writeTo(ClusterResponse response, ByteBuf out) {
+ int type = response.getType();
+ EntityWriter responseDataWriter = ResponseDataWriterRegistry.getWriter(type);
+
+ if (responseDataWriter == null) {
+ writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out);
+ RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{0}>", response.getType());
+ return;
+ }
+ writeHead(response, out);
+ responseDataWriter.writeTo(response.getData(), out);
+ }
+
+ private void writeHead(Response response, ByteBuf out) {
+ out.writeInt(response.getId());
+ out.writeByte(response.getType());
+ out.writeByte(response.getStatus());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java
new file mode 100644
index 00000000..ada283ab
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec;
+
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.SpiLoader;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ServerEntityCodecProvider {
+
+ private static RequestEntityDecoder requestEntityDecoder = null;
+ private static ResponseEntityWriter responseEntityWriter = null;
+
+ static {
+ resolveInstance();
+ }
+
+ private static void resolveInstance() {
+ ResponseEntityWriter writer = SpiLoader.loadFirstInstance(ResponseEntityWriter.class);
+ if (writer == null) {
+ RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed");
+ } else {
+ responseEntityWriter = writer;
+ RecordLog.info(
+ "[ServerEntityCodecProvider] Response entity writer resolved: " + responseEntityWriter.getClass()
+ .getCanonicalName());
+ }
+ RequestEntityDecoder decoder = SpiLoader.loadFirstInstance(RequestEntityDecoder.class);
+ if (decoder == null) {
+ RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed");
+ } else {
+ requestEntityDecoder = decoder;
+ RecordLog.info(
+ "[ServerEntityCodecProvider] Request entity decoder resolved: " + requestEntityDecoder.getClass()
+ .getCanonicalName());
+ }
+ }
+
+ public static RequestEntityDecoder getRequestEntityDecoder() {
+ return requestEntityDecoder;
+ }
+
+ public static ResponseEntityWriter getResponseEntityWriter() {
+ return responseEntityWriter;
+ }
+
+ private ServerEntityCodecProvider() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java
new file mode 100644
index 00000000..d34a7c6a
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.data;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.request.data.FlowRequestData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ *
+ * Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout:
+ *
+ *
+ * | flow ID (4) | count (4) | priority flag (1) |
+ *
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowRequestDataDecoder implements EntityDecoder {
+
+ @Override
+ public FlowRequestData decode(ByteBuf source) {
+ if (source.readableBytes() >= 12) {
+ FlowRequestData requestData = new FlowRequestData()
+ .setFlowId(source.readLong())
+ .setCount(source.readInt());
+ if (source.readableBytes() >= 1) {
+ requestData.setPriority(source.readBoolean());
+ }
+ return requestData;
+ }
+ // TODO: handle null here.
+ return null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java
new file mode 100644
index 00000000..0f240eef
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowResponseDataWriter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.data;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class FlowResponseDataWriter implements EntityWriter {
+
+ @Override
+ public void writeTo(FlowTokenResponseData entity, ByteBuf out) {
+ out.writeInt(entity.getRemainingCount());
+ out.writeInt(entity.getWaitInMs());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java
new file mode 100644
index 00000000..b98458cf
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/ParamFlowRequestDataDecoder.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+import com.alibaba.csp.sentinel.cluster.request.data.ParamFlowRequestData;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ */
+public class ParamFlowRequestDataDecoder implements EntityDecoder {
+
+ @Override
+ public ParamFlowRequestData decode(ByteBuf source) {
+ if (source.readableBytes() >= 16) {
+ ParamFlowRequestData requestData = new ParamFlowRequestData()
+ .setFlowId(source.readLong())
+ .setCount(source.readInt());
+
+ int amount = source.readInt();
+ if (amount > 0) {
+ // TODO: should check rules exist here?
+ List params = new ArrayList<>(amount);
+ for (int i = 0; i < amount; i++) {
+ decodeParam(source, params);
+ }
+
+ requestData.setParams(params);
+ return requestData;
+ }
+ }
+ // TODO: handle null here.
+ return null;
+ }
+
+ private boolean decodeParam(ByteBuf source, List params) {
+ byte paramType = source.readByte();
+
+ switch (paramType) {
+ case ClusterConstants.PARAM_TYPE_INTEGER:
+ params.add(source.readInt());
+ return true;
+ case ClusterConstants.PARAM_TYPE_STRING:
+ int length = source.readInt();
+ byte[] bytes = new byte[length];
+ source.readBytes(bytes);
+ // TODO: take care of charset?
+ params.add(new String(bytes));
+ return true;
+ case ClusterConstants.PARAM_TYPE_BOOLEAN:
+ params.add(source.readBoolean());
+ return true;
+ case ClusterConstants.PARAM_TYPE_DOUBLE:
+ params.add(source.readDouble());
+ return true;
+ case ClusterConstants.PARAM_TYPE_LONG:
+ params.add(source.readLong());
+ return true;
+ case ClusterConstants.PARAM_TYPE_FLOAT:
+ params.add(source.readFloat());
+ return true;
+ case ClusterConstants.PARAM_TYPE_BYTE:
+ params.add(source.readByte());
+ return true;
+ case ClusterConstants.PARAM_TYPE_SHORT:
+ params.add(source.readShort());
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java
new file mode 100644
index 00000000..d5257f19
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyRequestDecoder.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.netty;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.codec.request.RequestEntityDecoder;
+import com.alibaba.csp.sentinel.cluster.request.Request;
+import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class NettyRequestDecoder extends ByteToMessageDecoder {
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
+ RequestEntityDecoder requestDecoder = ServerEntityCodecProvider.getRequestEntityDecoder();
+ if (requestDecoder == null) {
+ // TODO: may need to throw exception?
+ RecordLog.warn("[NettyRequestDecoder] Cannot resolve the global request entity decoder, "
+ + "dropping the request");
+ return;
+ }
+
+ // TODO: handle decode error here.
+ Request request = requestDecoder.decode(in);
+ if (request != null) {
+ out.add(request);
+ }
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java
new file mode 100644
index 00000000..ef7e2bef
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/netty/NettyResponseEncoder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.netty;
+
+import com.alibaba.csp.sentinel.cluster.ClusterConstants;
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+import com.alibaba.csp.sentinel.cluster.codec.response.ResponseEntityWriter;
+import com.alibaba.csp.sentinel.cluster.response.ClusterResponse;
+import com.alibaba.csp.sentinel.cluster.response.Response;
+import com.alibaba.csp.sentinel.cluster.server.codec.ServerEntityCodecProvider;
+import com.alibaba.csp.sentinel.cluster.server.codec.registry.ResponseDataWriterRegistry;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class NettyResponseEncoder extends MessageToByteEncoder {
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, ClusterResponse response, ByteBuf out) throws Exception {
+ ResponseEntityWriter responseEntityWriter = ServerEntityCodecProvider.getResponseEntityWriter();
+ if (responseEntityWriter == null) {
+ RecordLog.warn("[NettyResponseEncoder] Cannot resolve the global response entity writer, reply bad status");
+ writeBadStatusHead(response, out);
+ return;
+ }
+
+ responseEntityWriter.writeTo(response, out);
+ }
+
+
+ private void writeBadStatusHead(Response response, ByteBuf out) {
+ out.writeInt(response.getId());
+ out.writeByte(ClusterConstants.RESPONSE_STATUS_BAD);
+ out.writeByte(response.getStatus());
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java
new file mode 100644
index 00000000..df8070d6
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/RequestDataDecodeRegistry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityDecoder;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class RequestDataDecodeRegistry {
+
+ private static final Map> DECODER_MAP = new HashMap<>();
+
+ public static boolean addDecoder(int type, EntityDecoder decoder) {
+ if (DECODER_MAP.containsKey(type)) {
+ return false;
+ }
+ DECODER_MAP.put(type, decoder);
+ return true;
+ }
+
+ public static EntityDecoder getDecoder(int type) {
+ return (EntityDecoder)DECODER_MAP.get(type);
+ }
+
+ public static boolean removeDecoder(int type) {
+ return DECODER_MAP.remove(type) != null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java
new file mode 100644
index 00000000..6b878b75
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/registry/ResponseDataWriterRegistry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.codec.registry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.alibaba.csp.sentinel.cluster.codec.EntityWriter;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ResponseDataWriterRegistry {
+
+ private static final Map> WRITER_MAP = new HashMap<>();
+
+ public static boolean addWriter(int type, EntityWriter writer) {
+ if (WRITER_MAP.containsKey(type)) {
+ return false;
+ }
+ WRITER_MAP.put(type, (EntityWriter)writer);
+ return true;
+ }
+
+ public static EntityWriter getWriter(int type) {
+ return WRITER_MAP.get(type);
+ }
+
+ public static boolean remove(int type) {
+ return WRITER_MAP.remove(type) != null;
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java
new file mode 100644
index 00000000..299a1275
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.config;
+
+/**
+ * @author Eric Zhao
+ */
+public final class ClusterServerConfigManager {
+
+ private static final int DEFAULT_PORT = 8730;
+ private static final int DEFAULT_IDLE_SECONDS = 600;
+
+ public static volatile int port = DEFAULT_PORT;
+
+ public static volatile double exceedCount = 1.0d;
+ public static volatile boolean borrowRefEnabled = true;
+ public static volatile int idleSeconds = DEFAULT_IDLE_SECONDS;
+ public static volatile double maxOccupyRatio = 1.0d;
+
+ // TODO: implement here.
+
+ private ClusterServerConfigManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java
new file mode 100644
index 00000000..29d6ecba
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/Connection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.net.SocketAddress;
+
+/**
+ * @author xuyue
+ * @author Eric Zhao
+ */
+public interface Connection extends AutoCloseable {
+
+ SocketAddress getLocalAddress();
+
+ int getRemotePort();
+
+ String getRemoteIP();
+
+ void refreshLastReadTime(long lastReadTime);
+
+ long getLastReadTime();
+
+ String getConnectionKey();
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java
new file mode 100644
index 00000000..6046a19c
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionGroup.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.csp.sentinel.cluster.server.ServerConstants;
+import com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ConnectionGroup {
+
+ private String namespace;
+
+ private Set addressSet = new ConcurrentSkipListSet<>();
+ private Set hostSet = new ConcurrentSkipListSet<>();
+ private AtomicInteger connectedCount = new AtomicInteger();
+
+ public ConnectionGroup(String namespace) {
+ AssertUtil.notEmpty(namespace, "namespace cannot be empty");
+ this.namespace = namespace;
+ }
+
+ public ConnectionGroup() {
+ this(ServerConstants.DEFAULT_NAMESPACE);
+ }
+
+ public ConnectionGroup addConnection(String address) {
+ AssertUtil.notEmpty(address, "address cannot be empty");
+
+ addressSet.add(address);
+ String[] ip = address.split(":");
+ if (ip != null && ip.length >= 1) {
+ hostSet.add(ip[0]);
+ }
+ connectedCount.incrementAndGet();
+ return this;
+ }
+
+ public ConnectionGroup removeConnection(String address) {
+ AssertUtil.notEmpty(address, "address cannot be empty");
+
+ addressSet.remove(address);
+ String[] ip = address.split(":");
+ if (ip != null && ip.length >= 1) {
+ hostSet.remove(ip[0]);
+ }
+ connectedCount.decrementAndGet();
+ return this;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public Set getAddressSet() {
+ return addressSet;
+ }
+
+ public Set getHostSet() {
+ return hostSet;
+ }
+
+ public int getConnectedCount() {
+ return connectedCount.get();
+ }
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java
new file mode 100644
index 00000000..100ba684
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public final class ConnectionManager {
+
+ private static final Map CONN_MAP = new ConcurrentHashMap<>();
+
+
+
+ private ConnectionManager() {}
+}
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java
new file mode 100644
index 00000000..4bc15dfb
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionPool.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+import io.netty.channel.Channel;
+
+/**
+ * Universal connection pool for connection management.
+ *
+ * @author xuyue
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ConnectionPool {
+
+ private static final ScheduledExecutorService TIMER = Executors.newScheduledThreadPool(2);
+
+ /**
+ * Format: ("ip:port", connection)
+ */
+ private final Map CONNECTION_MAP = new ConcurrentHashMap();
+
+ /**
+ * Periodic scan task.
+ */
+ private ScheduledFuture scanTaskFuture = null;
+
+ /**
+ * 创建一个connection,并放入连接池中
+ *
+ * @param channel
+ */
+ public void createConnection(Channel channel) {
+ if (channel != null) {
+ Connection connection = new NettyConnection(channel, this);
+
+ String connKey = getConnectionKey(channel);
+ CONNECTION_MAP.put(connKey, connection);
+ }
+ }
+
+ /**
+ * Start the scan task for long-idle connections.
+ */
+ private synchronized void startScan() {
+ if (scanTaskFuture == null
+ || scanTaskFuture.isCancelled()
+ || scanTaskFuture.isDone()) {
+ scanTaskFuture = TIMER.scheduleAtFixedRate(
+ new ScanIdleConnectionTask(this), 10, 30, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Format to "ip:port".
+ *
+ * @param channel channel
+ * @return formatted key
+ */
+ private String getConnectionKey(Channel channel) {
+ InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
+ String remoteIp = socketAddress.getAddress().getHostAddress();
+ int remotePort = socketAddress.getPort();
+ return remoteIp + ":" + remotePort;
+ }
+
+ private String getConnectionKey(String ip, int port) {
+ return ip + ":" + port;
+ }
+
+ /**
+ * 刷新一个连接上的最新read时间
+ *
+ * @param channel
+ */
+ public void refreshLastReadTime(Channel channel) {
+ if (channel != null) {
+ String connKey = getConnectionKey(channel);
+ Connection connection = CONNECTION_MAP.get(connKey);
+ //不应该为null,需要处理这种情况吗?
+ if (connection != null) {
+ connection.refreshLastReadTime(System.currentTimeMillis());
+ }
+ }
+ }
+
+ public Connection getConnection(String remoteIp, int remotePort) {
+ String connKey = getConnectionKey(remoteIp, remotePort);
+ return CONNECTION_MAP.get(connKey);
+ }
+
+ public void remove(Channel channel) {
+ String connKey = getConnectionKey(channel);
+ CONNECTION_MAP.remove(connKey);
+ }
+
+ public List listAllConnection() {
+ List connections = new ArrayList(CONNECTION_MAP.values());
+ return connections;
+ }
+
+ public int count(){
+ return CONNECTION_MAP.size();
+ }
+
+ public void clear() {
+ CONNECTION_MAP.clear();
+ }
+
+ public void shutdownAll() throws Exception {
+ for (Connection c : CONNECTION_MAP.values()) {
+ c.close();
+ }
+ }
+
+ public void refreshIdleTask() {
+ if (scanTaskFuture == null || scanTaskFuture.cancel(false)) {
+ startScan();
+ }else {
+ RecordLog.info("The result of canceling scanTask is error.");
+ }
+ }
+}
+
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java
new file mode 100644
index 00000000..3463871e
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/NettyConnection.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import io.netty.channel.Channel;
+
+/**
+ * @author xuyue
+ */
+public class NettyConnection implements Connection {
+
+ private String remoteIp;
+ private int remotePort;
+ private Channel channel;
+
+ private long lastReadTime;
+
+ private ConnectionPool pool;
+
+ public NettyConnection(Channel channel, ConnectionPool pool) {
+ this.channel = channel;
+ this.pool = pool;
+
+ InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
+ this.remoteIp = socketAddress.getAddress().getHostAddress();
+ this.remotePort = socketAddress.getPort();
+ this.lastReadTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public SocketAddress getLocalAddress() {
+ return channel.localAddress();
+ }
+
+ @Override
+ public int getRemotePort() {
+ return remotePort;
+ }
+
+ @Override
+ public String getRemoteIP() {
+ return remoteIp;
+ }
+
+ @Override
+ public void refreshLastReadTime(long lastReadTime) {
+ this.lastReadTime = lastReadTime;
+ }
+
+ @Override
+ public long getLastReadTime() {
+ return lastReadTime;
+ }
+
+ @Override
+ public String getConnectionKey() {
+ return remoteIp + ":" + remotePort;
+ }
+
+ @Override
+ public void close() {
+ // Remove from connection pool.
+ pool.remove(channel);
+ // Close the connection.
+ if (channel != null && channel.isActive()){
+ channel.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java
new file mode 100644
index 00000000..22af12d5
--- /dev/null
+++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java
@@ -0,0 +1,43 @@
+package com.alibaba.csp.sentinel.cluster.server.connection;
+
+import java.util.List;
+
+import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager;
+import com.alibaba.csp.sentinel.log.RecordLog;
+
+/**
+ * @author xuyue
+ * @author Eric Zhao
+ */
+public class ScanIdleConnectionTask implements Runnable {
+
+ private ConnectionPool connectionPool;
+
+ public ScanIdleConnectionTask(ConnectionPool connectionPool) {
+ this.connectionPool = connectionPool;
+ }
+
+ @Override
+ public void run() {
+ try {
+ int idleSeconds = ClusterServerConfigManager.idleSeconds;
+ long idleTime = idleSeconds * 1000;
+ if (idleTime < 0) {
+ idleTime = 600 * 1000;
+ }
+ long now = System.currentTimeMillis();
+ List