+ *
+ * @param fileName file name
+ * @param baseFileName base file name.
+ * @return if fileName matches baseFileName return true, else return false.
+ */
+ public static boolean fileNameMatches(String fileName, String baseFileName) {
+ if (fileName.startsWith(baseFileName)) {
+ String part = fileName.substring(baseFileName.length());
+ // part is like: ".yyyy-MM-dd.number", eg. ".2018-12-24.11"
+ return part.matches("\\.[0-9]{4}-[0-9]{2}-[0-9]{2}(\\.[0-9]*)?");
+ } else {
+ return false;
+ }
+ }
+
+ private void removeMoreFiles() throws Exception {
+ List list = listMetricFiles(baseDir, baseFileName);
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ for (int i = 0; i < list.size() - totalFileCount + 1; i++) {
+ String fileName = list.get(i);
+ String indexFile = formIndexFileName(fileName);
+ new File(fileName).delete();
+ RecordLog.info("[MetricWriter] Removing metric file: {}", fileName);
+ new File(indexFile).delete();
+ RecordLog.info("[MetricWriter] Removing metric index file: {}", indexFile);
+ }
+ }
+
+ private void closeAndNewFile(String fileName) throws Exception {
+ removeMoreFiles();
+ if (outMetricBuf != null) {
+ outMetricBuf.close();
+ }
+ if (outIndex != null) {
+ outIndex.close();
+ }
+ outMetric = new FileOutputStream(fileName, append);
+ outMetricBuf = new BufferedOutputStream(outMetric);
+ curMetricFile = new File(fileName);
+ String idxFile = formIndexFileName(fileName);
+ curMetricIndexFile = new File(idxFile);
+ outIndex = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile, append)));
+ RecordLog.info("[MetricWriter] New metric file created: {}", fileName);
+ RecordLog.info("[MetricWriter] New metric index file created: {}", idxFile);
+ }
+
+ private boolean validSize() throws Exception {
+ long size = outMetric.getChannel().size();
+ return size < singleFileSize;
+ }
+
+ private boolean isNewDay(long lastSecond, long second) {
+ long lastDay = (lastSecond - timeSecondBase) / 86400;
+ long newDay = (second - timeSecondBase) / 86400;
+ return newDay > lastDay;
+ }
+
+ /**
+ * Form metric file name use the specific appName and pid. Note that only
+ * form the file name, not include path.
+ *
+ * Note: {@link MetricFileNameComparator}'s implementation relays on the metric file name,
+ * we should be careful when changing the metric file name.
+ *
+ * @param appName
+ * @param pid
+ * @return metric file name.
+ */
+ public static String formMetricFileName(String appName, int pid) {
+ if (appName == null) {
+ appName = "";
+ }
+ // dot is special char that should be replaced.
+ final String dot = ".";
+ final String separator = "-";
+ if (appName.contains(dot)) {
+ appName = appName.replace(dot, separator);
+ }
+ String name = appName + separator + METRIC_FILE;
+ if (LogBase.isLogNameUsePid()) {
+ name += ".pid" + pid;
+ }
+ return name;
+ }
+
+ /**
+ * Form index file name of the {@code metricFileName}
+ *
+ * @param metricFileName
+ * @return the index file name of the metricFileName
+ */
+ public static String formIndexFileName(String metricFileName) {
+ return metricFileName + METRIC_FILE_INDEX_SUFFIX;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java
new file mode 100644
index 00000000..52e83c6c
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads metrics data from log file.
+ */
+class MetricsReader {
+
+ /**
+ * Avoid OOM in any cases.
+ */
+ private static final int MAX_LINES_RETURN = 100000;
+
+ private final Charset charset;
+
+ public MetricsReader(Charset charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * @return if should continue read, return true, else false.
+ */
+ boolean readMetricsInOneFileByEndTime(List list, String fileName, long offset,
+ long beginTimeMs, long endTimeMs, String identity) throws Exception {
+ FileInputStream in = null;
+ long beginSecond = beginTimeMs / 1000;
+ long endSecond = endTimeMs / 1000;
+ try {
+ in = new FileInputStream(fileName);
+ in.getChannel().position(offset);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ MetricNode node = MetricNode.fromFatString(line);
+ long currentSecond = node.getTimestamp() / 1000;
+ // currentSecond should >= beginSecond, otherwise a wrong metric file must occur
+ if (currentSecond < beginSecond) {
+ return false;
+ }
+ if (currentSecond <= endSecond) {
+ // read all
+ if (identity == null) {
+ list.add(node);
+ } else if (node.getResource().equals(identity)) {
+ list.add(node);
+ }
+ } else {
+ return false;
+ }
+ if (list.size() >= MAX_LINES_RETURN) {
+ return false;
+ }
+ }
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ return true;
+ }
+
+ void readMetricsInOneFile(List list, String fileName,
+ long offset, int recommendLines) throws Exception {
+ //if(list.size() >= recommendLines){
+ // return;
+ //}
+ long lastSecond = -1;
+ if (list.size() > 0) {
+ lastSecond = list.get(list.size() - 1).getTimestamp() / 1000;
+ }
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(fileName);
+ in.getChannel().position(offset);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ MetricNode node = MetricNode.fromFatString(line);
+ long currentSecond = node.getTimestamp() / 1000;
+
+ if (list.size() < recommendLines) {
+ list.add(node);
+ } else if (currentSecond == lastSecond) {
+ list.add(node);
+ } else {
+ break;
+ }
+ lastSecond = currentSecond;
+ }
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ }
+
+ /**
+ * When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific
+ * identity will be read.
+ */
+ List readMetricsByEndTime(List fileNames, int pos, long offset,
+ long beginTimeMs, long endTimeMs, String identity) throws Exception {
+ List list = new ArrayList(1024);
+ if (readMetricsInOneFileByEndTime(list, fileNames.get(pos++), offset, beginTimeMs, endTimeMs, identity)) {
+ while (pos < fileNames.size()
+ && readMetricsInOneFileByEndTime(list, fileNames.get(pos++), 0, beginTimeMs, endTimeMs, identity)) {
+ }
+ }
+ return list;
+ }
+
+ List readMetrics(List fileNames, int pos,
+ long offset, int recommendLines) throws Exception {
+ List list = new ArrayList(recommendLines);
+ readMetricsInOneFile(list, fileNames.get(pos++), offset, recommendLines);
+ while (list.size() < recommendLines && pos < fileNames.size()) {
+ readMetricsInOneFile(list, fileNames.get(pos++), 0, recommendLines);
+ }
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java
new file mode 100755
index 00000000..52dd1dc2
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class DynamicSentinelProperty implements SentinelProperty {
+
+ protected Set> listeners = new CopyOnWriteArraySet<>();
+ private T value = null;
+
+ public DynamicSentinelProperty() {
+ }
+
+ public DynamicSentinelProperty(T value) {
+ super();
+ this.value = value;
+ }
+
+ @Override
+ public void addListener(PropertyListener listener) {
+ listeners.add(listener);
+ listener.configLoad(value);
+ }
+
+ @Override
+ public void removeListener(PropertyListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public boolean updateValue(T newValue) {
+ if (isEqual(value, newValue)) {
+ return false;
+ }
+ RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue);
+
+ value = newValue;
+ for (PropertyListener listener : listeners) {
+ listener.configUpdate(newValue);
+ }
+ return true;
+ }
+
+ private boolean isEqual(T oldValue, T newValue) {
+ if (oldValue == null && newValue == null) {
+ return true;
+ }
+
+ if (oldValue == null) {
+ return false;
+ }
+
+ return oldValue.equals(newValue);
+ }
+
+ public void close() {
+ listeners.clear();
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java
new file mode 100755
index 00000000..395306c3
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property;
+
+/**
+ * A {@link SentinelProperty} that will never inform the {@link PropertyListener} on it.
+ *
+ * @author leyou
+ */
+public final class NoOpSentinelProperty implements SentinelProperty {
+
+ @Override
+ public void addListener(PropertyListener listener) { }
+
+ @Override
+ public void removeListener(PropertyListener listener) { }
+
+ @Override
+ public boolean updateValue(Object newValue) {
+ return true;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java
new file mode 100755
index 00000000..cce9871d
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java
@@ -0,0 +1,40 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty;
+
+/**
+ * This class holds callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener
+ *
+ * @author jialiang.linjl
+ */
+public interface PropertyListener {
+
+ /**
+ * Callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener.
+ *
+ * @param value updated value.
+ */
+ void configUpdate(T value);
+
+ /**
+ * The first time of the {@code value}'s load.
+ *
+ * @param value the value loaded.
+ */
+ void configLoad(T value);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.java
new file mode 100755
index 00000000..c2697e7f
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property;
+
+/**
+ *
+ * This class holds current value of the config, and is responsible for informing all {@link PropertyListener}s
+ * added on this when the config is updated.
+ *
+ *
+ * Note that not every {@link #updateValue(Object newValue)} invocation should inform the listeners, only when
+ * {@code newValue} is not Equals to the old value, informing is needed.
+ *
+ * Add a {@link PropertyListener} to this {@link SentinelProperty}. After the listener is added,
+ * {@link #updateValue(Object)} will inform the listener if needed.
+ *
+ *
+ * This method can invoke multi times to add more than one listeners.
+ *
+ *
+ * @param listener listener to add.
+ */
+ void addListener(PropertyListener listener);
+
+ /**
+ * Remove the {@link PropertyListener} on this. After removing, {@link #updateValue(Object)}
+ * will not inform the listener.
+ *
+ * @param listener the listener to remove.
+ */
+ void removeListener(PropertyListener listener);
+
+ /**
+ * Update the {@code newValue} as the current value of this property and inform all {@link PropertyListener}s
+ * added on this only when new {@code newValue} is not Equals to the old value.
+ *
+ * @param newValue the new value.
+ * @return true if the value in property has been updated, otherwise false
+ */
+ boolean updateValue(T newValue);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java
new file mode 100755
index 00000000..02af5a9a
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property;
+
+public abstract class SimplePropertyListener implements PropertyListener {
+
+ @Override
+ public void configLoad(T value) {
+ configUpdate(value);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java
new file mode 100755
index 00000000..a263d8f0
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java
@@ -0,0 +1,58 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+
+/**
+ * @author qinan.qn
+ * @author jialiang.linjl
+ */
+public abstract class AbstractLinkedProcessorSlot implements ProcessorSlot {
+
+ private AbstractLinkedProcessorSlot> next = null;
+
+ @Override
+ public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ if (next != null) {
+ next.transformEntry(context, resourceWrapper, obj, count, prioritized, args);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ T t = (T)o;
+ entry(context, resourceWrapper, t, count, prioritized, args);
+ }
+
+ @Override
+ public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ if (next != null) {
+ next.exit(context, resourceWrapper, count, args);
+ }
+ }
+
+ public AbstractLinkedProcessorSlot> getNext() {
+ return next;
+ }
+
+ public void setNext(AbstractLinkedProcessorSlot> next) {
+ this.next = next;
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java
new file mode 100755
index 00000000..119d3454
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+
+/**
+ * @author qinan.qn
+ * @author jialiang.linjl
+ */
+public class DefaultProcessorSlotChain extends ProcessorSlotChain {
+
+ AbstractLinkedProcessorSlot> first = new AbstractLinkedProcessorSlot() {
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ super.fireEntry(context, resourceWrapper, t, count, prioritized, args);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ super.fireExit(context, resourceWrapper, count, args);
+ }
+
+ };
+ AbstractLinkedProcessorSlot> end = first;
+
+ @Override
+ public void addFirst(AbstractLinkedProcessorSlot> protocolProcessor) {
+ protocolProcessor.setNext(first.getNext());
+ first.setNext(protocolProcessor);
+ if (end == first) {
+ end = protocolProcessor;
+ }
+ }
+
+ @Override
+ public void addLast(AbstractLinkedProcessorSlot> protocolProcessor) {
+ end.setNext(protocolProcessor);
+ end = protocolProcessor;
+ }
+
+ /**
+ * Same as {@link #addLast(AbstractLinkedProcessorSlot)}.
+ *
+ * @param next processor to be added.
+ */
+ @Override
+ public void setNext(AbstractLinkedProcessorSlot> next) {
+ addLast(next);
+ }
+
+ @Override
+ public AbstractLinkedProcessorSlot> getNext() {
+ return first.getNext();
+ }
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ first.transformEntry(context, resourceWrapper, t, count, prioritized, args);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ first.exit(context, resourceWrapper, count, args);
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java
new file mode 100755
index 00000000..492ab071
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import java.lang.reflect.Method;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.IdUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.MethodUtil;
+
+/**
+ * Resource wrapper for method invocation.
+ *
+ * @author qinan.qn
+ */
+public class MethodResourceWrapper extends ResourceWrapper {
+
+ private final transient Method method;
+
+ public MethodResourceWrapper(Method method, EntryType e) {
+ this(method, e, ResourceTypeConstants.COMMON);
+ }
+
+ public MethodResourceWrapper(Method method, EntryType e, int resType) {
+ super(MethodUtil.resolveMethodName(method), e, resType);
+ this.method = method;
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ @Override
+ public String getShowName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "MethodResourceWrapper{" +
+ "name='" + name + '\'' +
+ ", entryType=" + entryType +
+ ", resourceType=" + resourceType +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java
new file mode 100755
index 00000000..3b280df9
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java
@@ -0,0 +1,78 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+
+/**
+ * A container of some process and ways of notification when the process is finished.
+ *
+ * @author qinan.qn
+ * @author jialiang.linjl
+ * @author leyou(lihao)
+ * @author Eric Zhao
+ */
+public interface ProcessorSlot {
+
+ /**
+ * Entrance of this slot.
+ *
+ * @param context current {@link Context}
+ * @param resourceWrapper current resource
+ * @param param generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node}
+ * @param count tokens needed
+ * @param prioritized whether the entry is prioritized
+ * @param args parameters of the original call
+ * @throws Throwable blocked exception or unexpected error
+ */
+ void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized,
+ Object... args) throws Throwable;
+
+ /**
+ * Means finish of {@link #entry(Context, ResourceWrapper, Object, int, boolean, Object...)}.
+ *
+ * @param context current {@link Context}
+ * @param resourceWrapper current resource
+ * @param obj relevant object (e.g. Node)
+ * @param count tokens needed
+ * @param prioritized whether the entry is prioritized
+ * @param args parameters of the original call
+ * @throws Throwable blocked exception or unexpected error
+ */
+ void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized,
+ Object... args) throws Throwable;
+
+ /**
+ * Exit of this slot.
+ *
+ * @param context current {@link Context}
+ * @param resourceWrapper current resource
+ * @param count tokens needed
+ * @param args parameters of the original call
+ */
+ void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
+
+ /**
+ * Means finish of {@link #exit(Context, ResourceWrapper, int, Object...)}.
+ *
+ * @param context current {@link Context}
+ * @param resourceWrapper current resource
+ * @param count tokens needed
+ * @param args parameters of the original call
+ */
+ void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java
new file mode 100755
index 00000000..f10fadcf
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java
@@ -0,0 +1,40 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+
+/**
+ * Link all processor slots as a chain.
+ *
+ * @author qinan.qn
+ */
+public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot {
+
+ /**
+ * Add a processor to the head of this slot chain.
+ *
+ * @param protocolProcessor processor to be added.
+ */
+ public abstract void addFirst(AbstractLinkedProcessorSlot> protocolProcessor);
+
+ /**
+ * Add a processor to the tail of this slot chain.
+ *
+ * @param protocolProcessor processor to be added.
+ */
+ public abstract void addLast(AbstractLinkedProcessorSlot> protocolProcessor);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java
new file mode 100644
index 00000000..6400bd5b
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/**
+ * Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+public interface ProcessorSlotEntryCallback {
+
+ void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception;
+
+ void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java
new file mode 100644
index 00000000..8e323b12
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+
+/**
+ * Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked).
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+public interface ProcessorSlotExitCallback {
+
+ void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java
new file mode 100755
index 00000000..1271ed15
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ * A wrapper of resource name and type.
+ *
+ * @author qinan.qn
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public abstract class ResourceWrapper {
+
+ protected final String name;
+
+ protected final EntryType entryType;
+ protected final int resourceType;
+
+ public ResourceWrapper(String name, EntryType entryType, int resourceType) {
+ AssertUtil.notEmpty(name, "resource name cannot be empty");
+ AssertUtil.notNull(entryType, "entryType cannot be null");
+ this.name = name;
+ this.entryType = entryType;
+ this.resourceType = resourceType;
+ }
+
+ /**
+ * Get the resource name.
+ *
+ * @return the resource name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get {@link EntryType} of this wrapper.
+ *
+ * @return {@link EntryType} of this wrapper.
+ */
+ public EntryType getEntryType() {
+ return entryType;
+ }
+
+ /**
+ * Get the classification of this resource.
+ *
+ * @return the classification of this resource
+ * @since 1.7.0
+ */
+ public int getResourceType() {
+ return resourceType;
+ }
+
+ /**
+ * Get the beautified resource name to be showed.
+ *
+ * @return the beautified resource name
+ */
+ public abstract String getShowName();
+
+ /**
+ * Only {@link #getName()} is considered.
+ */
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ /**
+ * Only {@link #getName()} is considered.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ResourceWrapper) {
+ ResourceWrapper rw = (ResourceWrapper)obj;
+ return rw.getName().equals(getName());
+ }
+ return false;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java
new file mode 100755
index 00000000..5b8ec58c
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+
+/**
+ * The builder for processor slot chain.
+ *
+ * @author qinan.qn
+ * @author leyou
+ * @author Eric Zhao
+ */
+public interface SlotChainBuilder {
+
+ /**
+ * Build the processor slot chain.
+ *
+ * @return a processor slot that chain some slots together
+ */
+ ProcessorSlotChain build();
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java
new file mode 100644
index 00000000..3798921e
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader;
+
+/**
+ * A provider for creating slot chains via resolved slot chain builder SPI.
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+public final class SlotChainProvider {
+
+ private static volatile SlotChainBuilder slotChainBuilder = null;
+
+ /**
+ * The load and pick process is not thread-safe, but it's okay since the method should be only invoked
+ * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock.
+ *
+ * @return new created slot chain
+ */
+ public static ProcessorSlotChain newSlotChain() {
+ if (slotChainBuilder != null) {
+ return slotChainBuilder.build();
+ }
+
+ // Resolve the slot chain builder SPI.
+ slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault();
+
+ if (slotChainBuilder == null) {
+ // Should not go through here.
+ RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default");
+ slotChainBuilder = new DefaultSlotChainBuilder();
+ } else {
+ RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}",
+ slotChainBuilder.getClass().getCanonicalName());
+ }
+ return slotChainBuilder.build();
+ }
+
+ private SlotChainProvider() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java
new file mode 100755
index 00000000..f0cbc5f4
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants;
+
+/**
+ * Common string resource wrapper.
+ *
+ * @author qinan.qn
+ * @author jialiang.linjl
+ */
+public class StringResourceWrapper extends ResourceWrapper {
+
+ public StringResourceWrapper(String name, EntryType e) {
+ super(name, e, ResourceTypeConstants.COMMON);
+ }
+
+ public StringResourceWrapper(String name, EntryType e, int resType) {
+ super(name, e, resType);
+ }
+
+ @Override
+ public String getShowName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "StringResourceWrapper{" +
+ "name='" + name + '\'' +
+ ", entryType=" + entryType +
+ ", resourceType=" + resourceType +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java
new file mode 100755
index 00000000..c61fffa9
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainBuilder;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader;
+
+import java.util.List;
+
+/**
+ * Builder for a default {@link ProcessorSlotChain}.
+ *
+ * @author qinan.qn
+ * @author leyou
+ */
+@Spi(isDefault = true)
+public class DefaultSlotChainBuilder implements SlotChainBuilder {
+
+ @Override
+ public ProcessorSlotChain build() {
+ ProcessorSlotChain chain = new DefaultProcessorSlotChain();
+
+ List sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted();
+ for (ProcessorSlot slot : sortedSlotList) {
+ if (!(slot instanceof AbstractLinkedProcessorSlot)) {
+ RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain");
+ continue;
+ }
+
+ chain.addLast((AbstractLinkedProcessorSlot>) slot);
+ }
+
+ return chain;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java
new file mode 100755
index 00000000..666240f6
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java
@@ -0,0 +1,121 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+/**
+ * Abstract rule entity.
+ *
+ * @author youji.zj
+ * @author Eric Zhao
+ */
+public abstract class AbstractRule implements Rule {
+
+ /**
+ * rule id.
+ */
+ private Long id;
+
+ /**
+ * Resource name.
+ */
+ private String resource;
+
+ /**
+ *
+ * Application name that will be limited by origin.
+ * The default limitApp is {@code default}, which means allowing all origin apps.
+ *
+ *
+ * For authority rules, multiple origin name can be separated with comma (',').
+ *
+ */
+ private String limitApp;
+
+ public Long getId() {
+ return id;
+ }
+
+ public AbstractRule setId(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ @Override
+ public String getResource() {
+ return resource;
+ }
+
+ public AbstractRule setResource(String resource) {
+ this.resource = resource;
+ return this;
+ }
+
+ public String getLimitApp() {
+ return limitApp;
+ }
+
+ public AbstractRule setLimitApp(String limitApp) {
+ this.limitApp = limitApp;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof AbstractRule)) {
+ return false;
+ }
+
+ AbstractRule that = (AbstractRule)o;
+
+ if (resource != null ? !resource.equals(that.resource) : that.resource != null) {
+ return false;
+ }
+ if (!limitAppEquals(limitApp, that.limitApp)) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean limitAppEquals(String str1, String str2) {
+ if ("".equals(str1)) {
+ return RuleConstant.LIMIT_APP_DEFAULT.equals(str2);
+ } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(str1)) {
+ return "".equals(str2) || str2 == null || str1.equals(str2);
+ }
+ if (str1 == null) {
+ return str2 == null || RuleConstant.LIMIT_APP_DEFAULT.equals(str2);
+ }
+ return str1.equals(str2);
+ }
+
+ public T as(Class clazz) {
+ return (T)this;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = resource != null ? resource.hashCode() : 0;
+ if (!("".equals(limitApp) || RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp) || limitApp == null)) {
+ result = 31 * result + limitApp.hashCode();
+ }
+ return result;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java
new file mode 100755
index 00000000..a06be3fe
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java
@@ -0,0 +1,132 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block;
+
+/**
+ * Abstract exception indicating blocked by Sentinel due to flow control,
+ * circuit breaking or system protection triggered.
+ *
+ * @author youji.zj
+ */
+public abstract class BlockException extends Exception {
+
+ private static final int MAX_SEARCH_DEPTH = 10;
+
+ public static final String BLOCK_EXCEPTION_FLAG = "SentinelBlockException";
+ public static final String BLOCK_EXCEPTION_MSG_PREFIX = "SentinelBlockException: ";
+
+ /**
+ *
this constant RuntimeException has no stack trace, just has a message
+ * {@link #BLOCK_EXCEPTION_FLAG} that marks its name.
+ *
+ *
+ * Use {@link #isBlockException(Throwable)} to check whether one Exception
+ * Sentinel Blocked Exception.
+ *
the exception or its (sub-)cause is {@link BlockException}, or
+ *
the exception's message or any of its sub-cause's message is prefixed by {@link #BLOCK_EXCEPTION_FLAG}
+ *
+ *
+ * @param t the exception.
+ * @return return true if the exception marks sentinel blocked exception.
+ */
+ public static boolean isBlockException(Throwable t) {
+ if (null == t) {
+ return false;
+ }
+
+ int counter = 0;
+ Throwable cause = t;
+ while (cause != null && counter++ < MAX_SEARCH_DEPTH) {
+ if (cause instanceof BlockException) {
+ return true;
+ }
+ if (cause.getMessage() != null && cause.getMessage().startsWith(BLOCK_EXCEPTION_FLAG)) {
+ return true;
+ }
+ cause = cause.getCause();
+ }
+
+ return false;
+ }
+
+ public AbstractRule getRule() {
+ return rule;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java
new file mode 100644
index 00000000..d3971234
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.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.yeepay.yop.sdk.router.third.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_BORROW_REF = 1;
+
+ public static final int FLOW_THRESHOLD_AVG_LOCAL = 0;
+ public static final int FLOW_THRESHOLD_GLOBAL = 1;
+
+ public static final int DEFAULT_CLUSTER_SAMPLE_COUNT = 10;
+
+ private ClusterRuleConstant() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.java
new file mode 100755
index 00000000..8c1bd46d
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block;
+
+/**
+ * Base interface of all rules.
+ *
+ * @author youji.zj
+ */
+public interface Rule {
+
+ /**
+ * Get target resource of this rule.
+ *
+ * @return target resource of this rule
+ */
+ String getResource();
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
new file mode 100755
index 00000000..ef657f5b
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty;
+
+/**
+ * @author youji.zj
+ * @author jialiang.linjl
+ */
+public final class RuleConstant {
+
+ public static final int FLOW_GRADE_THREAD = 0;
+ public static final int FLOW_GRADE_QPS = 1;
+
+ public static final int DEGRADE_GRADE_RT = 0;
+ /**
+ * Degrade by biz exception ratio in the current {@link IntervalProperty#INTERVAL} second(s).
+ */
+ public static final int DEGRADE_GRADE_EXCEPTION_RATIO = 1;
+ /**
+ * Degrade by biz exception count in the last 60 seconds.
+ */
+ public static final int DEGRADE_GRADE_EXCEPTION_COUNT = 2;
+
+ public static final int DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT = 5;
+ public static final int DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT = 5;
+
+ public static final int AUTHORITY_WHITE = 0;
+ public static final int AUTHORITY_BLACK = 1;
+
+ public static final int STRATEGY_DIRECT = 0;
+ public static final int STRATEGY_RELATE = 1;
+ public static final int STRATEGY_CHAIN = 2;
+
+ public static final int CONTROL_BEHAVIOR_DEFAULT = 0;
+ public static final int CONTROL_BEHAVIOR_WARM_UP = 1;
+ public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2;
+ public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3;
+
+ public static final int DEFAULT_BLOCK_STRATEGY = 0;
+ public static final int TRY_AGAIN_BLOCK_STRATEGY = 1;
+ public static final int TRY_UNTIL_SUCCESS_BLOCK_STRATEGY = 2;
+
+ public static final int DEFAULT_RESOURCE_TIMEOUT_STRATEGY = 0;
+ public static final int RELEASE_RESOURCE_TIMEOUT_STRATEGY = 1;
+ public static final int KEEP_RESOURCE_TIMEOUT_STRATEGY = 2;
+
+ public static final String LIMIT_APP_DEFAULT = "default";
+ public static final String LIMIT_APP_OTHER = "other";
+
+ public static final int DEFAULT_SAMPLE_COUNT = 2;
+ public static final int DEFAULT_WINDOW_INTERVAL_MS = 1000;
+
+ private RuleConstant() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java
new file mode 100755
index 00000000..34333227
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block;
+
+/**
+ * A {@link RuntimeException} marks sentinel RPC exception. The stack trace
+ * is removed for high performance.
+ *
+ * @author leyou
+ */
+public class SentinelRpcException extends RuntimeException {
+
+ public SentinelRpcException(String msg) {
+ super(msg);
+ }
+
+ public SentinelRpcException(Throwable e) {
+ super(e);
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java
new file mode 100755
index 00000000..b38b2260
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/**
+ * Block exception for request origin access (authority) control.
+ *
+ * @author youji.zj
+ * @author Eric Zhao
+ */
+public class AuthorityException extends BlockException {
+
+ public AuthorityException(String ruleLimitApp) {
+ super(ruleLimitApp);
+ }
+
+ public AuthorityException(String ruleLimitApp, AuthorityRule rule) {
+ super(ruleLimitApp, rule);
+ }
+
+ public AuthorityException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AuthorityException(String ruleLimitApp, String message) {
+ super(ruleLimitApp, message);
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ /**
+ * Get triggered rule.
+ * Note: the rule result is a reference to rule map and SHOULD NOT be modified.
+ *
+ * @return triggered rule
+ * @since 1.4.2
+ */
+ @Override
+ public AuthorityRule getRule() {
+ return rule.as(AuthorityRule.class);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java
new file mode 100755
index 00000000..fb624021
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java
@@ -0,0 +1,68 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+/**
+ * Authority rule is designed for limiting by request origins.
+ *
+ * @author youji.zj
+ */
+public class AuthorityRule extends AbstractRule {
+
+ /**
+ * Mode: 0 for whitelist; 1 for blacklist.
+ */
+ private int strategy = RuleConstant.AUTHORITY_WHITE;
+
+ public int getStrategy() {
+ return strategy;
+ }
+
+ public AuthorityRule setStrategy(int strategy) {
+ this.strategy = strategy;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) { return true; }
+ if (!(o instanceof AuthorityRule)) { return false; }
+ if (!super.equals(o)) { return false; }
+
+ AuthorityRule rule = (AuthorityRule)o;
+
+ return strategy == rule.strategy;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + strategy;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "AuthorityRule{" +
+ "resource=" + getResource() +
+ ", limitApp=" + getLimitApp() +
+ ", strategy=" + strategy +
+ "} ";
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java
new file mode 100644
index 00000000..66ec1ca5
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java
@@ -0,0 +1,68 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * Rule checker for white/black list authority.
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+final class AuthorityRuleChecker {
+
+ static boolean passCheck(AuthorityRule rule, Context context) {
+ String requester = context.getOrigin();
+
+ // Empty origin or empty limitApp will pass.
+ if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) {
+ return true;
+ }
+
+ // Do exact match with origin name.
+ int pos = rule.getLimitApp().indexOf(requester);
+ boolean contain = pos > -1;
+
+ if (contain) {
+ boolean exactlyMatch = false;
+ String[] appArray = rule.getLimitApp().split(",");
+ for (String app : appArray) {
+ if (requester.equals(app)) {
+ exactlyMatch = true;
+ break;
+ }
+ }
+
+ contain = exactlyMatch;
+ }
+
+ int strategy = rule.getStrategy();
+ if (strategy == RuleConstant.AUTHORITY_BLACK && contain) {
+ return false;
+ }
+
+ if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private AuthorityRuleChecker() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java
new file mode 100755
index 00000000..5ee15fa8
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java
@@ -0,0 +1,151 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty;
+
+/**
+ * Manager for authority rules.
+ *
+ * @author youji.zj
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public final class AuthorityRuleManager {
+
+ private static volatile Map> authorityRules = new ConcurrentHashMap<>();
+
+ private static final RulePropertyListener LISTENER = new RulePropertyListener();
+ private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>();
+
+ static {
+ currentProperty.addListener(LISTENER);
+ }
+
+ public static void register2Property(SentinelProperty> property) {
+ AssertUtil.notNull(property, "property cannot be null");
+ synchronized (LISTENER) {
+ if (currentProperty != null) {
+ currentProperty.removeListener(LISTENER);
+ }
+ property.addListener(LISTENER);
+ currentProperty = property;
+ RecordLog.info("[AuthorityRuleManager] Registering new property to authority rule manager");
+ }
+ }
+
+ /**
+ * Load the authority rules to memory.
+ *
+ * @param rules list of authority rules
+ */
+ public static void loadRules(List rules) {
+ currentProperty.updateValue(rules);
+ }
+
+ public static boolean hasConfig(String resource) {
+ return authorityRules.containsKey(resource);
+ }
+
+ /**
+ * Get a copy of the rules.
+ *
+ * @return a new copy of the rules.
+ */
+ public static List getRules() {
+ List rules = new ArrayList<>();
+ if (authorityRules == null) {
+ return rules;
+ }
+ for (Map.Entry> entry : authorityRules.entrySet()) {
+ rules.addAll(entry.getValue());
+ }
+ return rules;
+ }
+
+ private static class RulePropertyListener implements PropertyListener> {
+
+ @Override
+ public synchronized void configLoad(List value) {
+ authorityRules = loadAuthorityConf(value);
+
+ RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules);
+ }
+
+ @Override
+ public synchronized void configUpdate(List conf) {
+ authorityRules = loadAuthorityConf(conf);
+
+ RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);
+ }
+
+ private Map> loadAuthorityConf(List list) {
+ Map> newRuleMap = new ConcurrentHashMap<>();
+
+ if (list == null || list.isEmpty()) {
+ return newRuleMap;
+ }
+
+ for (AuthorityRule rule : list) {
+ if (!isValidRule(rule)) {
+ RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule);
+ continue;
+ }
+
+ if (StringUtil.isBlank(rule.getLimitApp())) {
+ rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ String identity = rule.getResource();
+ Set ruleSet = newRuleMap.get(identity);
+ // putIfAbsent
+ if (ruleSet == null) {
+ ruleSet = new HashSet<>();
+ ruleSet.add(rule);
+ newRuleMap.put(identity, ruleSet);
+ } else {
+ // One resource should only have at most one authority rule, so just ignore redundant rules.
+ RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString());
+ }
+ }
+
+ return newRuleMap;
+ }
+
+ }
+
+ static Map> getAuthorityRules() {
+ return authorityRules;
+ }
+
+ public static boolean isValidRule(AuthorityRule rule) {
+ return rule != null && !StringUtil.isBlank(rule.getResource())
+ && rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp());
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java
new file mode 100755
index 00000000..f57888f8
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleChecker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+
+/**
+ * A {@link ProcessorSlot} that dedicates to {@link AuthorityRule} checking.
+ *
+ * @author leyou
+ * @author Eric Zhao
+ */
+@Spi(order = Constants.ORDER_AUTHORITY_SLOT)
+public class AuthoritySlot extends AbstractLinkedProcessorSlot {
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ checkBlackWhiteAuthority(resourceWrapper, context);
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ fireExit(context, resourceWrapper, count, args);
+ }
+
+ void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException {
+ Map> authorityRules = AuthorityRuleManager.getAuthorityRules();
+
+ if (authorityRules == null) {
+ return;
+ }
+
+ Set rules = authorityRules.get(resource.getName());
+ if (rules == null) {
+ return;
+ }
+
+ for (AuthorityRule rule : rules) {
+ if (!AuthorityRuleChecker.passCheck(rule, context)) {
+ throw new AuthorityException(context.getOrigin(), rule);
+ }
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java
new file mode 100755
index 00000000..81f7373e
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/***
+ * @author youji.zj
+ */
+public class DegradeException extends BlockException {
+
+ public DegradeException(String ruleLimitApp) {
+ super(ruleLimitApp);
+ }
+
+ public DegradeException(String ruleLimitApp, DegradeRule rule) {
+ super(ruleLimitApp, rule);
+ }
+
+ public DegradeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DegradeException(String ruleLimitApp, String message) {
+ super(ruleLimitApp, message);
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ /**
+ * Get triggered rule.
+ * Note: the rule result is a reference to rule map and SHOULD NOT be modified.
+ *
+ * @return triggered rule
+ * @since 1.4.2
+ */
+ @Override
+ public DegradeRule getRule() {
+ return rule.as(DegradeRule.class);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java
new file mode 100755
index 00000000..6dfa059c
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+import java.util.Objects;
+
+/**
+ *
+ * Degrade is used when the resources are in an unstable state, these resources
+ * will be degraded within the next defined time window. There are two ways to
+ * measure whether a resource is stable or not:
+ *
+ *
+ *
+ * Average response time ({@code DEGRADE_GRADE_RT}): When
+ * the average RT exceeds the threshold ('count' in 'DegradeRule', in milliseconds), the
+ * resource enters a quasi-degraded state. If the RT of next coming 5
+ * requests still exceed this threshold, this resource will be downgraded, which
+ * means that in the next time window (defined in 'timeWindow', in seconds) all the
+ * access to this resource will be blocked.
+ *
+ *
+ * Exception ratio: When the ratio of exception count per second and the
+ * success qps exceeds the threshold, access to the resource will be blocked in
+ * the coming window.
+ *
+ *
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class DegradeRule extends AbstractRule {
+
+ public DegradeRule() {}
+
+ public DegradeRule(String resourceName) {
+ setResource(resourceName);
+ }
+
+ /**
+ * Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count).
+ */
+ private int grade = RuleConstant.DEGRADE_GRADE_RT;
+
+ /**
+ * Threshold count. The exact meaning depends on the field of grade.
+ *
+ *
In average RT mode, it means the maximum response time(RT) in milliseconds.
+ *
In exception ratio mode, it means exception ratio which between 0.0 and 1.0.
+ *
In exception count mode, it means exception count
+ *
+ */
+ private double count;
+
+ /**
+ * Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will
+ * transform to half-open state for trying a few requests.
+ */
+ private int timeWindow;
+
+ /**
+ * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
+ *
+ * @since 1.7.0
+ */
+ private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;
+
+ /**
+ * The threshold of slow request ratio in RT mode.
+ *
+ * @since 1.8.0
+ */
+ private double slowRatioThreshold = 1.0d;
+
+ /**
+ * The interval statistics duration in millisecond.
+ *
+ * @since 1.8.0
+ */
+ private int statIntervalMs = 1000;
+
+ public int getGrade() {
+ return grade;
+ }
+
+ public DegradeRule setGrade(int grade) {
+ this.grade = grade;
+ return this;
+ }
+
+ public double getCount() {
+ return count;
+ }
+
+ public DegradeRule setCount(double count) {
+ this.count = count;
+ return this;
+ }
+
+ public int getTimeWindow() {
+ return timeWindow;
+ }
+
+ public DegradeRule setTimeWindow(int timeWindow) {
+ this.timeWindow = timeWindow;
+ return this;
+ }
+
+ public int getMinRequestAmount() {
+ return minRequestAmount;
+ }
+
+ public DegradeRule setMinRequestAmount(int minRequestAmount) {
+ this.minRequestAmount = minRequestAmount;
+ return this;
+ }
+
+ public double getSlowRatioThreshold() {
+ return slowRatioThreshold;
+ }
+
+ public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) {
+ this.slowRatioThreshold = slowRatioThreshold;
+ return this;
+ }
+
+ public int getStatIntervalMs() {
+ return statIntervalMs;
+ }
+
+ public DegradeRule setStatIntervalMs(int statIntervalMs) {
+ this.statIntervalMs = statIntervalMs;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+ if (!super.equals(o)) { return false; }
+ DegradeRule rule = (DegradeRule)o;
+ return Double.compare(rule.count, count) == 0 &&
+ timeWindow == rule.timeWindow &&
+ grade == rule.grade &&
+ minRequestAmount == rule.minRequestAmount &&
+ Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 &&
+ statIntervalMs == rule.statIntervalMs;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount,
+ slowRatioThreshold, statIntervalMs);
+ }
+
+ @Override
+ public String toString() {
+ return "DegradeRule{" +
+ "resource=" + getResource() +
+ ", grade=" + grade +
+ ", count=" + count +
+ ", limitApp=" + getLimitApp() +
+ ", timeWindow=" + timeWindow +
+ ", minRequestAmount=" + minRequestAmount +
+ ", slowRatioThreshold=" + slowRatioThreshold +
+ ", statIntervalMs=" + statIntervalMs +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java
new file mode 100755
index 00000000..64be18e0
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java
@@ -0,0 +1,268 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+
+/**
+ * The rule manager for circuit breaking rules ({@link DegradeRule}).
+ *
+ * @author youji.zj
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public final class DegradeRuleManager {
+
+ private static volatile Map> circuitBreakers = new HashMap<>();
+ private static volatile Map> ruleMap = new HashMap<>();
+
+ private static final RulePropertyListener LISTENER = new RulePropertyListener();
+ private static SentinelProperty> currentProperty
+ = new DynamicSentinelProperty<>();
+
+ static {
+ currentProperty.addListener(LISTENER);
+ }
+
+ /**
+ * Listen to the {@link SentinelProperty} for {@link DegradeRule}s. The property is the source
+ * of {@link DegradeRule}s. Degrade rules can also be set by {@link #loadRules(List)} directly.
+ *
+ * @param property the property to listen.
+ */
+ public static void register2Property(SentinelProperty> property) {
+ AssertUtil.notNull(property, "property cannot be null");
+ synchronized (LISTENER) {
+ RecordLog.info("[DegradeRuleManager] Registering new property to degrade rule manager");
+ currentProperty.removeListener(LISTENER);
+ property.addListener(LISTENER);
+ currentProperty = property;
+ }
+ }
+
+ static List getCircuitBreakers(String resourceName) {
+ return circuitBreakers.get(resourceName);
+ }
+
+ public static boolean hasConfig(String resource) {
+ if (resource == null) {
+ return false;
+ }
+ return circuitBreakers.containsKey(resource);
+ }
+
+ /**
+ *
Get existing circuit breaking rules.
+ *
Note: DO NOT modify the rules from the returned list directly.
+ * The behavior is undefined.
+ *
+ * @return list of existing circuit breaking rules, or empty list if no rules were loaded
+ */
+ public static List getRules() {
+ List rules = new ArrayList<>();
+ for (Map.Entry> entry : ruleMap.entrySet()) {
+ rules.addAll(entry.getValue());
+ }
+ return rules;
+ }
+
+ public static Set getRulesOfResource(String resource) {
+ AssertUtil.assertNotBlank(resource, "resource name cannot be blank");
+ return ruleMap.get(resource);
+ }
+
+ /**
+ * Load {@link DegradeRule}s, former rules will be replaced.
+ *
+ * @param rules new rules to load.
+ */
+ public static void loadRules(List rules) {
+ try {
+ currentProperty.updateValue(rules);
+ } catch (Throwable e) {
+ RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", e);
+ }
+ }
+
+ /**
+ * Set degrade rules for provided resource. Former rules of the resource will be replaced.
+ *
+ * @param resourceName valid resource name
+ * @param rules new rule set to load
+ * @return whether the rules has actually been updated
+ * @since 1.5.0
+ */
+ public static boolean setRulesForResource(String resourceName, Set rules) {
+ AssertUtil.notEmpty(resourceName, "resourceName cannot be empty");
+ try {
+ Map> newRuleMap = new HashMap<>(ruleMap);
+ if (rules == null) {
+ newRuleMap.remove(resourceName);
+ } else {
+ Set newSet = new HashSet<>();
+ for (DegradeRule rule : rules) {
+ if (isValidRule(rule) && resourceName.equals(rule.getResource())) {
+ newSet.add(rule);
+ }
+ }
+ newRuleMap.put(resourceName, newSet);
+ }
+ List allRules = new ArrayList<>();
+ for (Set set : newRuleMap.values()) {
+ allRules.addAll(set);
+ }
+ return currentProperty.updateValue(allRules);
+ } catch (Throwable e) {
+ RecordLog.error("[DegradeRuleManager] Unexpected error when setting circuit breaking"
+ + " rules for resource: " + resourceName, e);
+ return false;
+ }
+ }
+
+ private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) {
+ List cbs = getCircuitBreakers(rule.getResource());
+ if (cbs == null || cbs.isEmpty()) {
+ return newCircuitBreakerFrom(rule);
+ }
+ for (CircuitBreaker cb : cbs) {
+ if (rule.equals(cb.getRule())) {
+ // Reuse the circuit breaker if the rule remains unchanged.
+ return cb;
+ }
+ }
+ return newCircuitBreakerFrom(rule);
+ }
+
+ /**
+ * Create a circuit breaker instance from provided circuit breaking rule.
+ *
+ * @param rule a valid circuit breaking rule
+ * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type
+ */
+ private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) {
+ switch (rule.getGrade()) {
+ case RuleConstant.DEGRADE_GRADE_RT:
+ return new ResponseTimeCircuitBreaker(rule);
+ case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
+ case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
+ return new ExceptionCircuitBreaker(rule);
+ default:
+ return null;
+ }
+ }
+
+ public static boolean isValidRule(DegradeRule rule) {
+ boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource())
+ && rule.getCount() >= 0 && rule.getTimeWindow() > 0;
+ if (!baseValid) {
+ return false;
+ }
+ if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) {
+ return false;
+ }
+ switch (rule.getGrade()) {
+ case RuleConstant.DEGRADE_GRADE_RT:
+ return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1;
+ case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO:
+ return rule.getCount() <= 1;
+ case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static class RulePropertyListener implements PropertyListener> {
+
+ private synchronized void reloadFrom(List list) {
+ Map> cbs = buildCircuitBreakers(list);
+ Map> rm = new HashMap<>(cbs.size());
+
+ for (Map.Entry> e : cbs.entrySet()) {
+ assert e.getValue() != null && !e.getValue().isEmpty();
+
+ Set rules = new HashSet<>(e.getValue().size());
+ for (CircuitBreaker cb : e.getValue()) {
+ rules.add(cb.getRule());
+ }
+ rm.put(e.getKey(), rules);
+ }
+
+ DegradeRuleManager.circuitBreakers = cbs;
+ DegradeRuleManager.ruleMap = rm;
+ }
+
+ @Override
+ public void configUpdate(List conf) {
+ reloadFrom(conf);
+ RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: {}", ruleMap);
+ }
+
+ @Override
+ public void configLoad(List conf) {
+ reloadFrom(conf);
+ RecordLog.info("[DegradeRuleManager] Degrade rules loaded: {}", ruleMap);
+ }
+
+ private Map> buildCircuitBreakers(List list) {
+ Map> cbMap = new HashMap<>(8);
+ if (list == null || list.isEmpty()) {
+ return cbMap;
+ }
+ for (DegradeRule rule : list) {
+ if (!isValidRule(rule)) {
+ RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: {}", rule);
+ continue;
+ }
+
+ if (StringUtil.isBlank(rule.getLimitApp())) {
+ rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+ CircuitBreaker cb = getExistingSameCbOrNew(rule);
+ if (cb == null) {
+ RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: {}", rule);
+ continue;
+ }
+
+ String resourceName = rule.getResource();
+
+ List cbList = cbMap.get(resourceName);
+ if (cbList == null) {
+ cbList = new ArrayList<>();
+ cbMap.put(resourceName, cbList);
+ }
+ cbList.add(cb);
+ }
+ return cbMap;
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java
new file mode 100755
index 00000000..00e35cc0
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade;
+
+import java.util.List;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+
+/**
+ * A {@link ProcessorSlot} dedicates to circuit breaking.
+ *
+ * @author Carpenter Lee
+ * @author Eric Zhao
+ */
+@Spi(order = Constants.ORDER_DEGRADE_SLOT)
+public class DegradeSlot extends AbstractLinkedProcessorSlot {
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
+ boolean prioritized, Object... args) throws Throwable {
+ performChecking(context, resourceWrapper);
+
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+ }
+
+ void performChecking(Context context, ResourceWrapper r) throws BlockException {
+ List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
+ if (circuitBreakers == null || circuitBreakers.isEmpty()) {
+ return;
+ }
+ for (CircuitBreaker cb : circuitBreakers) {
+ if (!cb.tryPass(context)) {
+ throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule());
+ }
+ }
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper r, int count, Object... args) {
+ Entry curEntry = context.getCurEntry();
+ if (curEntry.getBlockError() != null) {
+ fireExit(context, r, count, args);
+ return;
+ }
+ List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName());
+ if (circuitBreakers == null || circuitBreakers.isEmpty()) {
+ fireExit(context, r, count, args);
+ return;
+ }
+
+ if (curEntry.getBlockError() == null) {
+ // passed request
+ for (CircuitBreaker circuitBreaker : circuitBreakers) {
+ circuitBreaker.onRequestComplete(context);
+ }
+ }
+
+ fireExit(context, r, count, args);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java
new file mode 100644
index 00000000..d35f0293
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 1999-2019 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.BiConsumer;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public abstract class AbstractCircuitBreaker implements CircuitBreaker {
+
+ protected final DegradeRule rule;
+ protected final int recoveryTimeoutMs;
+
+ private final EventObserverRegistry observerRegistry;
+
+ protected final AtomicReference currentState = new AtomicReference<>(State.CLOSED);
+ protected volatile long nextRetryTimestamp;
+
+ public AbstractCircuitBreaker(DegradeRule rule) {
+ this(rule, EventObserverRegistry.getInstance());
+ }
+
+ AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) {
+ AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null");
+ if (!DegradeRuleManager.isValidRule(rule)) {
+ throw new IllegalArgumentException("Invalid DegradeRule: " + rule);
+ }
+ this.observerRegistry = observerRegistry;
+ this.rule = rule;
+ this.recoveryTimeoutMs = rule.getTimeWindow() * 1000;
+ }
+
+ @Override
+ public DegradeRule getRule() {
+ return rule;
+ }
+
+ @Override
+ public State currentState() {
+ return currentState.get();
+ }
+
+ @Override
+ public boolean tryPass(Context context) {
+ // Template implementation.
+ if (currentState.get() == State.CLOSED) {
+ return true;
+ }
+ if (currentState.get() == State.OPEN) {
+ // For half-open state we allow a request for probing.
+ return retryTimeoutArrived() && fromOpenToHalfOpen(context);
+ }
+ return false;
+ }
+
+ /**
+ * Reset the statistic data.
+ */
+ abstract void resetStat();
+
+ protected boolean retryTimeoutArrived() {
+ return TimeUtil.currentTimeMillis() >= nextRetryTimestamp;
+ }
+
+ protected void updateNextRetryTimestamp() {
+ this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs;
+ }
+
+ protected boolean fromCloseToOpen(double snapshotValue) {
+ State prev = State.CLOSED;
+ if (currentState.compareAndSet(prev, State.OPEN)) {
+ updateNextRetryTimestamp();
+
+ notifyObservers(prev, State.OPEN, snapshotValue);
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean fromOpenToHalfOpen(Context context) {
+ if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) {
+ notifyObservers(State.OPEN, State.HALF_OPEN, null);
+ Entry entry = context.getCurEntry();
+ entry.whenTerminate(new BiConsumer() {
+ @Override
+ public void accept(Context context, Entry entry) {
+ // Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638
+ // Without the hook, the circuit breaker won't recover from half-open state in some circumstances
+ // when the request is actually blocked by upcoming rules (not only degrade rules).
+ if (entry.getBlockError() != null) {
+ // Fallback to OPEN due to detecting request is blocked
+ currentState.compareAndSet(State.HALF_OPEN, State.OPEN);
+ notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d);
+ }
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ private void notifyObservers(CircuitBreaker.State prevState, CircuitBreaker.State newState, Double snapshotValue) {
+ for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) {
+ observer.onStateChange(prevState, newState, rule, snapshotValue);
+ }
+ }
+
+ protected boolean fromHalfOpenToOpen(double snapshotValue) {
+ if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) {
+ updateNextRetryTimestamp();
+ notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue);
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean fromHalfOpenToClose() {
+ if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) {
+ resetStat();
+ notifyObservers(State.HALF_OPEN, State.CLOSED, null);
+ return true;
+ }
+ return false;
+ }
+
+ protected void transformToOpen(double triggerValue) {
+ State cs = currentState.get();
+ switch (cs) {
+ case CLOSED:
+ fromCloseToOpen(triggerValue);
+ break;
+ case HALF_OPEN:
+ fromHalfOpenToOpen(triggerValue);
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java
new file mode 100644
index 00000000..f141d9ca
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 1999-2019 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
+ *
+ * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+
+/**
+ *
+ *
+ * @author Eric Zhao
+ */
+public interface CircuitBreaker {
+
+ /**
+ * Get the associated circuit breaking rule.
+ *
+ * @return associated circuit breaking rule
+ */
+ DegradeRule getRule();
+
+ /**
+ * Acquires permission of an invocation only if it is available at the time of invoking.
+ *
+ * @param context context of current invocation
+ * @return {@code true} if permission was acquired and {@code false} otherwise
+ */
+ boolean tryPass(Context context);
+
+ /**
+ * Get current state of the circuit breaker.
+ *
+ * @return current state of the circuit breaker
+ */
+ State currentState();
+
+ /**
+ *
Record a completed request with the context and handle state transformation of the circuit breaker.
+ *
Called when a passed invocation finished.
+ *
+ * @param context context of current invocation
+ */
+ void onRequestComplete(Context context);
+
+ /**
+ * Circuit breaker state.
+ */
+ enum State {
+ /**
+ * In {@code OPEN} state, all requests will be rejected until the next recovery time point.
+ */
+ OPEN,
+ /**
+ * In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation.
+ * If the invocation is abnormal according to the strategy (e.g. it's slow), the circuit breaker
+ * will re-transform to the {@code OPEN} state and wait for the next recovery time point;
+ * otherwise the resource will be regarded as "recovered" and the circuit breaker
+ * will cease cutting off requests and transform to {@code CLOSED} state.
+ */
+ HALF_OPEN,
+ /**
+ * In {@code CLOSED} state, all requests are permitted. When current metric value exceeds the threshold,
+ * the circuit breaker will transform to {@code OPEN} state.
+ */
+ CLOSED
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java
new file mode 100644
index 00000000..85fea2c2
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2019 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+
+/**
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public interface CircuitBreakerStateChangeObserver {
+
+ /**
+ *
Observer method triggered when circuit breaker state changed. The transformation could be:
+ *
+ *
From {@code CLOSED} to {@code OPEN} (with the triggered metric)
+ *
From {@code OPEN} to {@code HALF_OPEN}
+ *
From {@code OPEN} to {@code CLOSED}
+ *
From {@code HALF_OPEN} to {@code OPEN} (with the triggered metric)
+ *
+ *
+ * @param prevState previous state of the circuit breaker
+ * @param newState new state of the circuit breaker
+ * @param rule associated rule
+ * @param snapshotValue triggered value on circuit breaker opens (null if the new state is CLOSED or HALF_OPEN)
+ */
+ void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule,
+ Double snapshotValue);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java
new file mode 100644
index 00000000..fe89971b
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1999-2020 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
+ *
+ * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+/**
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public enum CircuitBreakerStrategy {
+
+ /**
+ * Circuit breaker opens (cuts off) when slow request ratio exceeds the threshold.
+ */
+ SLOW_REQUEST_RATIO(0),
+ /**
+ * Circuit breaker opens (cuts off) when error ratio exceeds the threshold.
+ */
+ ERROR_RATIO(1),
+ /**
+ * Circuit breaker opens (cuts off) when error count exceeds the threshold.
+ */
+ ERROR_COUNT(2);
+
+ private int type;
+
+ CircuitBreakerStrategy(int type) {
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java
new file mode 100644
index 00000000..1f408d36
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 1999-2020 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
+ *
+ * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+
+/**
+ *
Registry for circuit breaker event observers.
+ *
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public class EventObserverRegistry {
+
+ private final Map stateChangeObserverMap = new HashMap<>();
+
+ /**
+ * Register a circuit breaker state change observer.
+ *
+ * @param name observer name
+ * @param observer a valid observer
+ */
+ public void addStateChangeObserver(String name, CircuitBreakerStateChangeObserver observer) {
+ AssertUtil.notNull(name, "name cannot be null");
+ AssertUtil.notNull(observer, "observer cannot be null");
+ stateChangeObserverMap.put(name, observer);
+ }
+
+ public boolean removeStateChangeObserver(String name) {
+ AssertUtil.notNull(name, "name cannot be null");
+ return stateChangeObserverMap.remove(name) != null;
+ }
+
+ /**
+ * Get all registered state chane observers.
+ *
+ * @return all registered state chane observers
+ */
+ public List getStateChangeObservers() {
+ return new ArrayList<>(stateChangeObserverMap.values());
+ }
+
+ public static EventObserverRegistry getInstance() {
+ return InstanceHolder.instance;
+ }
+
+ private static class InstanceHolder {
+ private static EventObserverRegistry instance = new EventObserverRegistry();
+ }
+
+ EventObserverRegistry() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java
new file mode 100644
index 00000000..fcc129b7
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 1999-2019 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import java.util.List;
+import java.util.concurrent.atomic.LongAdder;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+
+import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT;
+import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;
+
+/**
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {
+
+ private final int strategy;
+ private final int minRequestAmount;
+ private final double threshold;
+
+ private final LeapArray stat;
+
+ public ExceptionCircuitBreaker(DegradeRule rule) {
+ this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
+ }
+
+ ExceptionCircuitBreaker(DegradeRule rule, LeapArray stat) {
+ super(rule);
+ this.strategy = rule.getGrade();
+ boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT;
+ AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count");
+ AssertUtil.notNull(stat, "stat cannot be null");
+ this.minRequestAmount = rule.getMinRequestAmount();
+ this.threshold = rule.getCount();
+ this.stat = stat;
+ }
+
+ @Override
+ protected void resetStat() {
+ // Reset current bucket (bucket count = 1).
+ stat.currentWindow().value().reset();
+ }
+
+ @Override
+ public void onRequestComplete(Context context) {
+ Entry entry = context.getCurEntry();
+ if (entry == null) {
+ return;
+ }
+ Throwable error = entry.getError();
+ SimpleErrorCounter counter = stat.currentWindow().value();
+ if (error != null) {
+ counter.getErrorCount().add(1);
+ }
+ counter.getTotalCount().add(1);
+
+ handleStateChangeWhenThresholdExceeded(error);
+ }
+
+ private void handleStateChangeWhenThresholdExceeded(Throwable error) {
+ if (currentState.get() == State.OPEN) {
+ return;
+ }
+
+ if (currentState.get() == State.HALF_OPEN) {
+ // In detecting request
+ if (error == null) {
+ fromHalfOpenToClose();
+ } else {
+ fromHalfOpenToOpen(1.0d);
+ }
+ return;
+ }
+
+ List counters = stat.values();
+ long errCount = 0;
+ long totalCount = 0;
+ for (SimpleErrorCounter counter : counters) {
+ errCount += counter.errorCount.sum();
+ totalCount += counter.totalCount.sum();
+ }
+ if (totalCount < minRequestAmount) {
+ return;
+ }
+ double curCount = errCount;
+ if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
+ // Use errorRatio
+ curCount = errCount * 1.0d / totalCount;
+ }
+ if (curCount > threshold) {
+ transformToOpen(curCount);
+ }
+ }
+
+ static class SimpleErrorCounter {
+ private LongAdder errorCount;
+ private LongAdder totalCount;
+
+ public SimpleErrorCounter() {
+ this.errorCount = new LongAdder();
+ this.totalCount = new LongAdder();
+ }
+
+ public LongAdder getErrorCount() {
+ return errorCount;
+ }
+
+ public LongAdder getTotalCount() {
+ return totalCount;
+ }
+
+ public SimpleErrorCounter reset() {
+ errorCount.reset();
+ totalCount.reset();
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "SimpleErrorCounter{" +
+ "errorCount=" + errorCount +
+ ", totalCount=" + totalCount +
+ '}';
+ }
+ }
+
+ static class SimpleErrorCounterLeapArray extends LeapArray {
+
+ public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) {
+ super(sampleCount, intervalInMs);
+ }
+
+ @Override
+ public SimpleErrorCounter newEmptyBucket(long timeMillis) {
+ return new SimpleErrorCounter();
+ }
+
+ @Override
+ protected WindowWrap resetWindowTo(WindowWrap w, long startTime) {
+ // Update the start time and reset value.
+ w.resetTo(startTime);
+ w.value().reset();
+ return w;
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java
new file mode 100644
index 00000000..b270ea61
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 1999-2019 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker;
+
+import java.util.List;
+import java.util.concurrent.atomic.LongAdder;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+
+/**
+ * @author Eric Zhao
+ * @since 1.8.0
+ */
+public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker {
+
+ private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d;
+
+ private final long maxAllowedRt;
+ private final double maxSlowRequestRatio;
+ private final int minRequestAmount;
+
+ private final LeapArray slidingCounter;
+
+ public ResponseTimeCircuitBreaker(DegradeRule rule) {
+ this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs()));
+ }
+
+ ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray stat) {
+ super(rule);
+ AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT");
+ AssertUtil.notNull(stat, "stat cannot be null");
+ this.maxAllowedRt = Math.round(rule.getCount());
+ this.maxSlowRequestRatio = rule.getSlowRatioThreshold();
+ this.minRequestAmount = rule.getMinRequestAmount();
+ this.slidingCounter = stat;
+ }
+
+ @Override
+ public void resetStat() {
+ // Reset current bucket (bucket count = 1).
+ slidingCounter.currentWindow().value().reset();
+ }
+
+ @Override
+ public void onRequestComplete(Context context) {
+ SlowRequestCounter counter = slidingCounter.currentWindow().value();
+ Entry entry = context.getCurEntry();
+ if (entry == null) {
+ return;
+ }
+ long completeTime = entry.getCompleteTimestamp();
+ if (completeTime <= 0) {
+ completeTime = TimeUtil.currentTimeMillis();
+ }
+ long rt = completeTime - entry.getCreateTimestamp();
+ if (rt > maxAllowedRt) {
+ counter.slowCount.add(1);
+ }
+ counter.totalCount.add(1);
+
+ handleStateChangeWhenThresholdExceeded(rt);
+ }
+
+ private void handleStateChangeWhenThresholdExceeded(long rt) {
+ if (currentState.get() == State.OPEN) {
+ return;
+ }
+
+ if (currentState.get() == State.HALF_OPEN) {
+ // In detecting request
+ // TODO: improve logic for half-open recovery
+ if (rt > maxAllowedRt) {
+ fromHalfOpenToOpen(1.0d);
+ } else {
+ fromHalfOpenToClose();
+ }
+ return;
+ }
+
+ List counters = slidingCounter.values();
+ long slowCount = 0;
+ long totalCount = 0;
+ for (SlowRequestCounter counter : counters) {
+ slowCount += counter.slowCount.sum();
+ totalCount += counter.totalCount.sum();
+ }
+ if (totalCount < minRequestAmount) {
+ return;
+ }
+ double currentRatio = slowCount * 1.0d / totalCount;
+ if (currentRatio > maxSlowRequestRatio) {
+ transformToOpen(currentRatio);
+ }
+ if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 &&
+ Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) {
+ transformToOpen(currentRatio);
+ }
+ }
+
+ static class SlowRequestCounter {
+ private LongAdder slowCount;
+ private LongAdder totalCount;
+
+ public SlowRequestCounter() {
+ this.slowCount = new LongAdder();
+ this.totalCount = new LongAdder();
+ }
+
+ public LongAdder getSlowCount() {
+ return slowCount;
+ }
+
+ public LongAdder getTotalCount() {
+ return totalCount;
+ }
+
+ public SlowRequestCounter reset() {
+ slowCount.reset();
+ totalCount.reset();
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "SlowRequestCounter{" +
+ "slowCount=" + slowCount +
+ ", totalCount=" + totalCount +
+ '}';
+ }
+ }
+
+ static class SlowRequestLeapArray extends LeapArray {
+
+ public SlowRequestLeapArray(int sampleCount, int intervalInMs) {
+ super(sampleCount, intervalInMs);
+ }
+
+ @Override
+ public SlowRequestCounter newEmptyBucket(long timeMillis) {
+ return new SlowRequestCounter();
+ }
+
+ @Override
+ protected WindowWrap resetWindowTo(WindowWrap w, long startTime) {
+ w.resetTo(startTime);
+ w.value().reset();
+ return w;
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
new file mode 100644
index 00000000..91aff4dc
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java
@@ -0,0 +1,233 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+import java.util.Objects;
+
+/**
+ * Flow rule config in cluster mode.
+ *
+ * @author Eric Zhao
+ * @since 1.4.0
+ */
+public class ClusterFlowConfig {
+
+ /**
+ * 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 = true;
+
+ /**
+ * 0: normal.
+ */
+ private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL;
+
+ private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT;
+ /**
+ * The time interval length of the statistic sliding window (in milliseconds)
+ */
+ private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS;
+
+ /**
+ * if the client keep the token for more than resourceTimeout,resourceTimeoutStrategy will work.
+ */
+ private long resourceTimeout = 2000;
+
+ /**
+ * 0:ignore,1:release the token.
+ */
+ private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY;
+
+ /**
+ * if the request(prioritized=true) is block,acquireRefuseStrategy will work..
+ * 0:ignore and block.
+ * 1:try again .
+ * 2:try until success.
+ */
+ private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY;
+
+ /**
+ * if a client is offline,the server will delete all the token the client holds after clientOfflineTime.
+ */
+ private long clientOfflineTime = 2000;
+
+ public long getResourceTimeout() {
+ return resourceTimeout;
+ }
+
+ public void setResourceTimeout(long resourceTimeout) {
+ this.resourceTimeout = resourceTimeout;
+ }
+
+ public int getResourceTimeoutStrategy() {
+ return resourceTimeoutStrategy;
+ }
+
+ public void setResourceTimeoutStrategy(int resourceTimeoutStrategy) {
+ this.resourceTimeoutStrategy = resourceTimeoutStrategy;
+ }
+
+ public int getAcquireRefuseStrategy() {
+ return acquireRefuseStrategy;
+ }
+
+ public void setAcquireRefuseStrategy(int acquireRefuseStrategy) {
+ this.acquireRefuseStrategy = acquireRefuseStrategy;
+ }
+
+ public long getClientOfflineTime() {
+ return clientOfflineTime;
+ }
+
+ public void setClientOfflineTime(long clientOfflineTime) {
+ this.clientOfflineTime = clientOfflineTime;
+ }
+
+ public Long getFlowId() {
+ return flowId;
+ }
+
+ public ClusterFlowConfig setFlowId(Long 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 boolean isFallbackToLocalWhenFail() {
+ return fallbackToLocalWhenFail;
+ }
+
+ public ClusterFlowConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) {
+ this.fallbackToLocalWhenFail = fallbackToLocalWhenFail;
+ return this;
+ }
+
+ public int getSampleCount() {
+ return sampleCount;
+ }
+
+ public ClusterFlowConfig setSampleCount(int sampleCount) {
+ this.sampleCount = sampleCount;
+ return this;
+ }
+
+ public int getWindowIntervalMs() {
+ return windowIntervalMs;
+ }
+
+ public ClusterFlowConfig setWindowIntervalMs(int windowIntervalMs) {
+ this.windowIntervalMs = windowIntervalMs;
+ 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 (sampleCount != that.sampleCount) {
+ return false;
+ }
+ if (windowIntervalMs != that.windowIntervalMs) {
+ return false;
+ }
+ if (resourceTimeout != that.resourceTimeout) {
+ return false;
+ }
+ if (clientOfflineTime != that.clientOfflineTime) {
+ return false;
+ }
+ if (resourceTimeoutStrategy != that.resourceTimeoutStrategy) {
+ return false;
+ }
+ if (acquireRefuseStrategy != that.acquireRefuseStrategy) {
+ return false;
+ }
+ return Objects.equals(flowId, that.flowId);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = flowId != null ? flowId.hashCode() : 0;
+ result = 31 * result + thresholdType;
+ result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0);
+ result = 31 * result + strategy;
+ result = 31 * result + sampleCount;
+ result = 31 * result + windowIntervalMs;
+ result = (int) (31 * result + resourceTimeout);
+ result = (int) (31 * result + clientOfflineTime);
+ result = 31 * result + resourceTimeoutStrategy;
+ result = 31 * result + acquireRefuseStrategy;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ClusterFlowConfig{" +
+ "flowId=" + flowId +
+ ", thresholdType=" + thresholdType +
+ ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail +
+ ", strategy=" + strategy +
+ ", sampleCount=" + sampleCount +
+ ", windowIntervalMs=" + windowIntervalMs +
+ ", resourceTimeout=" + resourceTimeout +
+ ", resourceTimeoutStrategy=" + resourceTimeoutStrategy +
+ ", acquireRefuseStrategy=" + acquireRefuseStrategy +
+ ", clientOfflineTime=" + clientOfflineTime +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java
new file mode 100755
index 00000000..ff18b8cd
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java
@@ -0,0 +1,26 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig;
+
+/**
+ * @author jialiang.linjl
+ */
+class ColdFactorProperty {
+
+ public static volatile int coldFactor = SentinelConfig.coldFactor();
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java
new file mode 100755
index 00000000..37c106b9
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/***
+ * @author youji.zj
+ */
+public class FlowException extends BlockException {
+
+ public FlowException(String ruleLimitApp) {
+ super(ruleLimitApp);
+ }
+
+ public FlowException(String ruleLimitApp, FlowRule rule) {
+ super(ruleLimitApp, rule);
+ }
+
+ public FlowException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FlowException(String ruleLimitApp, String message) {
+ super(ruleLimitApp, message);
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+
+ /**
+ * Get triggered rule.
+ * Note: the rule result is a reference to rule map and SHOULD NOT be modified.
+ *
+ * @return triggered rule
+ * @since 1.4.2
+ */
+ @Override
+ public FlowRule getRule() {
+ return rule.as(FlowRule.class);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java
new file mode 100755
index 00000000..6e1bc0ff
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java
@@ -0,0 +1,242 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+
+/**
+ *
+ * Each flow rule is mainly composed of three factors: grade,
+ * strategy and controlBehavior:
+ *
+ *
+ *
The {@link #grade} represents the threshold type of flow control (by QPS or thread count).
+ *
The {@link #strategy} represents the strategy based on invocation relation.
+ *
The {@link #controlBehavior} represents the QPS shaping behavior (actions on incoming request when QPS
+ * exceeds the threshold).
+ *
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class FlowRule extends AbstractRule {
+
+ public FlowRule() {
+ super();
+ setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ public FlowRule(String resourceName) {
+ super();
+ setResource(resourceName);
+ setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
+ }
+
+ /**
+ * The threshold type of flow control (0: thread count, 1: QPS).
+ */
+ private int grade = RuleConstant.FLOW_GRADE_QPS;
+
+ /**
+ * Flow control threshold count.
+ */
+ private double count;
+
+ /**
+ * Flow control strategy based on invocation chain.
+ *
+ * {@link RuleConstant#STRATEGY_DIRECT} for direct flow control (by origin);
+ * {@link RuleConstant#STRATEGY_RELATE} for relevant flow control (with relevant resource);
+ * {@link RuleConstant#STRATEGY_CHAIN} for chain flow control (by entrance resource).
+ */
+ private int strategy = RuleConstant.STRATEGY_DIRECT;
+
+ /**
+ * Reference resource in flow control with relevant resource or context.
+ */
+ private String refResource;
+
+ /**
+ * Rate limiter control behavior.
+ * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
+ */
+ private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
+
+ private int warmUpPeriodSec = 10;
+
+ /**
+ * Max queueing time in rate limiter behavior.
+ */
+ private int maxQueueingTimeMs = 500;
+
+ private boolean clusterMode;
+ /**
+ * Flow rule config for cluster mode.
+ */
+ private ClusterFlowConfig clusterConfig;
+
+ /**
+ * The traffic shaping (throttling) controller.
+ */
+ private TrafficShapingController controller;
+
+ public int getControlBehavior() {
+ return controlBehavior;
+ }
+
+ public FlowRule setControlBehavior(int controlBehavior) {
+ this.controlBehavior = controlBehavior;
+ return this;
+ }
+
+ public int getMaxQueueingTimeMs() {
+ return maxQueueingTimeMs;
+ }
+
+ public FlowRule setMaxQueueingTimeMs(int maxQueueingTimeMs) {
+ this.maxQueueingTimeMs = maxQueueingTimeMs;
+ return this;
+ }
+
+ FlowRule setRater(TrafficShapingController rater) {
+ this.controller = rater;
+ return this;
+ }
+
+ TrafficShapingController getRater() {
+ return controller;
+ }
+
+ public int getWarmUpPeriodSec() {
+ return warmUpPeriodSec;
+ }
+
+ public FlowRule setWarmUpPeriodSec(int warmUpPeriodSec) {
+ this.warmUpPeriodSec = warmUpPeriodSec;
+ return this;
+ }
+
+ public int getGrade() {
+ return grade;
+ }
+
+ public FlowRule setGrade(int grade) {
+ this.grade = grade;
+ return this;
+ }
+
+ public double getCount() {
+ return count;
+ }
+
+ public FlowRule setCount(double count) {
+ this.count = count;
+ return this;
+ }
+
+ public int getStrategy() {
+ return strategy;
+ }
+
+ public FlowRule setStrategy(int strategy) {
+ this.strategy = strategy;
+ return this;
+ }
+
+ public String getRefResource() {
+ return refResource;
+ }
+
+ public FlowRule setRefResource(String refResource) {
+ this.refResource = refResource;
+ 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 equals(Object o) {
+ if (this == o) { return true; }
+ if (o == null || getClass() != o.getClass()) { return false; }
+ if (!super.equals(o)) { return false; }
+
+ FlowRule rule = (FlowRule)o;
+
+ if (grade != rule.grade) { return false; }
+ if (Double.compare(rule.count, count) != 0) { return false; }
+ if (strategy != rule.strategy) { return false; }
+ if (controlBehavior != rule.controlBehavior) { return false; }
+ if (warmUpPeriodSec != rule.warmUpPeriodSec) { return false; }
+ if (maxQueueingTimeMs != rule.maxQueueingTimeMs) { return false; }
+ if (clusterMode != rule.clusterMode) { return false; }
+ if (refResource != null ? !refResource.equals(rule.refResource) : rule.refResource != null) { return false; }
+ return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ long temp;
+ result = 31 * result + grade;
+ temp = Double.doubleToLongBits(count);
+ result = 31 * result + (int)(temp ^ (temp >>> 32));
+ result = 31 * result + strategy;
+ result = 31 * result + (refResource != null ? refResource.hashCode() : 0);
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return "FlowRule{" +
+ "resource=" + getResource() +
+ ", limitApp=" + getLimitApp() +
+ ", grade=" + grade +
+ ", count=" + count +
+ ", strategy=" + strategy +
+ ", refResource=" + refResource +
+ ", controlBehavior=" + controlBehavior +
+ ", warmUpPeriodSec=" + warmUpPeriodSec +
+ ", maxQueueingTimeMs=" + maxQueueingTimeMs +
+ ", clusterMode=" + clusterMode +
+ ", clusterConfig=" + clusterConfig +
+ ", controller=" + controller +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java
new file mode 100644
index 00000000..2bd681a0
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java
@@ -0,0 +1,211 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import java.util.Collection;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.ClusterStateManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client.TokenClientProvider;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenResultStatus;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenResult;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenService;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function;
+
+/**
+ * Rule checker for flow control rules.
+ *
+ * @author Eric Zhao
+ */
+public class FlowRuleChecker {
+
+ public void checkFlow(Function> ruleProvider, ResourceWrapper resource,
+ Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
+ if (ruleProvider == null || resource == null) {
+ return;
+ }
+ Collection rules = ruleProvider.apply(resource.getName());
+ if (rules != null) {
+ for (FlowRule rule : rules) {
+ if (!canPassCheck(rule, context, node, count, prioritized)) {
+ throw new FlowException(rule.getLimitApp(), rule);
+ }
+ }
+ }
+ }
+
+ public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node,
+ int acquireCount) {
+ return canPassCheck(rule, context, node, acquireCount, false);
+ }
+
+ public boolean canPassCheck(/*@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;
+ }
+
+ return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
+ }
+
+ static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) {
+ String refResource = rule.getRefResource();
+ int strategy = rule.getStrategy();
+
+ if (StringUtil.isEmpty(refResource)) {
+ return null;
+ }
+
+ if (strategy == RuleConstant.STRATEGY_RELATE) {
+ return ClusterBuilderSlot.getClusterNode(refResource);
+ }
+
+ if (strategy == RuleConstant.STRATEGY_CHAIN) {
+ if (!refResource.equals(context.getName())) {
+ return null;
+ }
+ return node;
+ }
+ // No node.
+ return null;
+ }
+
+ private static boolean filterOrigin(String origin) {
+ // Origin cannot be `default` or `other`.
+ return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin);
+ }
+
+ static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) {
+ // The limit app should not be empty.
+ String limitApp = rule.getLimitApp();
+ int strategy = rule.getStrategy();
+ String origin = context.getOrigin();
+
+ if (limitApp.equals(origin) && filterOrigin(origin)) {
+ if (strategy == RuleConstant.STRATEGY_DIRECT) {
+ // Matches limit origin, return origin statistic node.
+ return context.getOriginNode();
+ }
+
+ return selectReferenceNode(rule, context, node);
+ } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
+ if (strategy == RuleConstant.STRATEGY_DIRECT) {
+ // Return the cluster node.
+ return node.getClusterNode();
+ }
+
+ return selectReferenceNode(rule, context, node);
+ } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
+ && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
+ if (strategy == RuleConstant.STRATEGY_DIRECT) {
+ return context.getOriginNode();
+ }
+
+ return selectReferenceNode(rule, context, node);
+ }
+
+ return null;
+ }
+
+ private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,
+ boolean prioritized) {
+ try {
+ TokenService clusterService = pickClusterService();
+ if (clusterService == null) {
+ return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
+ }
+ long flowId = rule.getClusterConfig().getFlowId();
+ TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);
+ return applyTokenResult(result, rule, context, node, acquireCount, prioritized);
+ // If client is absent, then fallback to local mode.
+ } catch (Throwable ex) {
+ RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);
+ }
+ // Fallback to local flow control when token client or server for this rule is not available.
+ // If fallback is not enabled, then directly pass.
+ return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
+ }
+
+ private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount,
+ boolean prioritized) {
+ if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {
+ return passLocalCheck(rule, context, node, acquireCount, prioritized);
+ } else {
+ // The rule won't be activated, just pass.
+ return true;
+ }
+ }
+
+ private static TokenService pickClusterService() {
+ if (ClusterStateManager.isClient()) {
+ return TokenClientProvider.getClient();
+ }
+ if (ClusterStateManager.isServer()) {
+ return EmbeddedClusterTokenServerProvider.getServer();
+ }
+ return null;
+ }
+
+ private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context,
+ DefaultNode node,
+ int acquireCount, boolean prioritized) {
+ switch (result.getStatus()) {
+ case TokenResultStatus.OK:
+ return true;
+ case TokenResultStatus.SHOULD_WAIT:
+ // Wait for next tick.
+ try {
+ Thread.sleep(result.getWaitInMs());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return true;
+ case TokenResultStatus.NO_RULE_EXISTS:
+ case TokenResultStatus.BAD_REQUEST:
+ case TokenResultStatus.FAIL:
+ case TokenResultStatus.TOO_MANY_REQUEST:
+ return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);
+ case TokenResultStatus.BLOCKED:
+ default:
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java
new file mode 100755
index 00000000..b51047f8
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import java.util.Comparator;
+
+import com.yeepay.yop.sdk.router.third.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) {
+ // the FlowRule in Clustered mode will be put at the end.
+ if (o1.isClusterMode() && !o2.isClusterMode()) {
+ return 1;
+ }
+
+ if (!o1.isClusterMode() && o2.isClusterMode()) {
+ return -1;
+ }
+
+ if (o1.getLimitApp() == null) {
+ return 0;
+ }
+
+ if (o1.getLimitApp().equals(o2.getLimitApp())) {
+ return 0;
+ }
+
+ if (RuleConstant.LIMIT_APP_DEFAULT.equals(o1.getLimitApp())) {
+ return 1;
+ } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java
new file mode 100755
index 00000000..4df8d24e
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java
@@ -0,0 +1,171 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricTimerListener;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ * One resources can have multiple rules. And these rules take effects in the following order:
+ *
+ *
requests from specified caller
+ *
no specified caller
+ *
+ *
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ * @author Weihua
+ */
+public class FlowRuleManager {
+
+ private static volatile Map> flowRules = new HashMap<>();
+
+ private static final FlowPropertyListener LISTENER = new FlowPropertyListener();
+ private static SentinelProperty> currentProperty = new DynamicSentinelProperty>();
+
+ /** the corePool size of SCHEDULER must be set at 1, so the two task ({@link #startMetricTimerListener()} can run orderly by the SCHEDULER **/
+ @SuppressWarnings("PMD.ThreadPoolCreationRule")
+ private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
+ new NamedThreadFactory("sentinel-metrics-record-task", true));
+
+ static {
+ currentProperty.addListener(LISTENER);
+ startMetricTimerListener();
+ }
+
+ /**
+ *
Start the MetricTimerListener
+ *
+ *
If the flushInterval more than 0,
+ * the timer will run with the flushInterval as the rate
.
+ *
If the flushInterval less than 0(include) or value is not valid,
+ * then means the timer will not be started
+ *
+ */
+ private static void startMetricTimerListener() {
+ long flushInterval = SentinelConfig.metricLogFlushIntervalSec();
+ if (flushInterval <= 0) {
+ RecordLog.info("[FlowRuleManager] The MetricTimerListener isn't started. If you want to start it, "
+ + "please change the value(current: {}) of config({}) more than 0 to start it.", flushInterval,
+ SentinelConfig.METRIC_FLUSH_INTERVAL);
+ return;
+ }
+ SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, flushInterval, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Listen to the {@link SentinelProperty} for {@link FlowRule}s. The property is the source of {@link FlowRule}s.
+ * Flow rules can also be set by {@link #loadRules(List)} directly.
+ *
+ * @param property the property to listen.
+ */
+ public static void register2Property(SentinelProperty> property) {
+ AssertUtil.notNull(property, "property cannot be null");
+ synchronized (LISTENER) {
+ RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager");
+ currentProperty.removeListener(LISTENER);
+ property.addListener(LISTENER);
+ currentProperty = property;
+ }
+ }
+
+ /**
+ * Get a copy of the rules.
+ *
+ * @return a new copy of the rules.
+ */
+ public static List getRules() {
+ List rules = new ArrayList();
+ for (Map.Entry> entry : flowRules.entrySet()) {
+ rules.addAll(entry.getValue());
+ }
+ return rules;
+ }
+
+ /**
+ * Load {@link FlowRule}s, former rules will be replaced.
+ *
+ * @param rules new rules to load.
+ */
+ public static void loadRules(List rules) {
+ currentProperty.updateValue(rules);
+ }
+
+ static Map> getFlowRuleMap() {
+ return flowRules;
+ }
+
+ public static boolean hasConfig(String resource) {
+ return flowRules.containsKey(resource);
+ }
+
+ public static boolean isOtherOrigin(String origin, String resourceName) {
+ if (StringUtil.isEmpty(origin)) {
+ return false;
+ }
+
+ List rules = flowRules.get(resourceName);
+
+ if (rules != null) {
+ for (FlowRule rule : rules) {
+ if (origin.equals(rule.getLimitApp())) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static final class FlowPropertyListener implements PropertyListener> {
+
+ @Override
+ public synchronized void configUpdate(List value) {
+ Map> rules = FlowRuleUtil.buildFlowRuleMap(value);
+ if (rules != null) {
+ flowRules = rules;
+ }
+ RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules);
+ }
+
+ @Override
+ public synchronized void configLoad(List conf) {
+ Map> rules = FlowRuleUtil.buildFlowRuleMap(conf);
+ if (rules != null) {
+ flowRules = rules;
+ }
+ RecordLog.info("[FlowRuleManager] Flow rules loaded: {}", rules);
+ }
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
new file mode 100644
index 00000000..e8a9df99
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java
@@ -0,0 +1,266 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ColdFactorProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleComparator;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @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;
+ }
+ Map> tmpMap = new ConcurrentHashMap<>();
+
+ 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;
+ }
+ Set flowRules = tmpMap.get(key);
+
+ if (flowRules == null) {
+ // Use hash set here to remove duplicate rules.
+ flowRules = new HashSet<>();
+ tmpMap.put(key, flowRules);
+ }
+
+ flowRules.add(rule);
+ }
+ Comparator comparator = new FlowRuleComparator();
+ for (Entry> entries : tmpMap.entrySet()) {
+ List rules = new ArrayList<>(entries.getValue());
+ if (shouldSort) {
+ // Sort the rules.
+ Collections.sort(rules, comparator);
+ }
+ newRuleMap.put(entries.getKey(), rules);
+ }
+
+ 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(Long 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;
+ }
+ if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) {
+ // Check strategy and control (shaping) behavior.
+ return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule);
+ } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) {
+ return checkClusterConcurrentField(rule);
+ } else {
+ return false;
+ }
+
+ }
+
+ public static boolean checkClusterConcurrentField(/*@NonNull*/ FlowRule rule) {
+ if (!rule.isClusterMode()) {
+ return true;
+ }
+ ClusterFlowConfig clusterConfig = rule.getClusterConfig();
+ if (clusterConfig == null) {
+ return false;
+ }
+ if (clusterConfig.getClientOfflineTime() <= 0 || clusterConfig.getResourceTimeout() <= 0) {
+ return false;
+ }
+
+ if (clusterConfig.getAcquireRefuseStrategy() < 0 || clusterConfig.getResourceTimeoutStrategy() < 0) {
+ return false;
+ }
+
+ if (!validClusterRuleId(clusterConfig.getFlowId())) {
+ return false;
+ }
+
+ return isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs());
+ }
+
+ 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;
+ }
+ if (!isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) {
+ return false;
+ }
+ switch (clusterConfig.getStrategy()) {
+ case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isWindowConfigValid(int sampleCount, int windowIntervalMs) {
+ return sampleCount > 0 && windowIntervalMs > 0 && windowIntervalMs % sampleCount == 0;
+ }
+
+ 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/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java
new file mode 100755
index 00000000..d7067214
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java
@@ -0,0 +1,189 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ * Combined the runtime statistics collected from the previous
+ * slots (NodeSelectorSlot, ClusterNodeBuilderSlot, and StatisticSlot), FlowSlot
+ * will use pre-set rules to decide whether the incoming requests should be
+ * blocked.
+ *
+ *
+ *
+ * {@code SphU.entry(resourceName)} will throw {@code FlowException} if any rule is
+ * triggered. Users can customize their own logic by catching {@code FlowException}.
+ *
+ *
+ *
+ * One resource can have multiple flow rules. FlowSlot traverses these rules
+ * until one of them is triggered or all rules have been traversed.
+ *
+ *
+ *
+ * Each {@link FlowRule} is mainly composed of these factors: grade, strategy, path. We
+ * can combine these factors to achieve different effects.
+ *
+ *
+ *
+ * The grade is defined by the {@code grade} field in {@link FlowRule}. Here, 0 for thread
+ * isolation and 1 for request count shaping (QPS). Both thread count and request
+ * count are collected in real runtime, and we can view these statistics by
+ * following command:
+ *
{@code thread} for the count of threads that is currently processing the resource
+ *
{@code pass} for the count of incoming request within one second
+ *
{@code blocked} for the count of requests blocked within one second
+ *
{@code success} for the count of the requests successfully handled by Sentinel within one second
+ *
{@code RT} for the average response time of the requests within a second
+ *
{@code total} for the sum of incoming requests and blocked requests within one second
+ *
{@code 1m-pass} is for the count of incoming requests within one minute
+ *
{@code 1m-block} is for the count of a request blocked within one minute
+ *
{@code 1m-all} is the total of incoming and blocked requests within one minute
+ *
{@code exception} is for the count of business (customized) exceptions in one second
+ *
+ *
+ * This stage is usually used to protect resources from occupying. If a resource
+ * takes long time to finish, threads will begin to occupy. The longer the
+ * response takes, the more threads occupy.
+ *
+ * Besides counter, thread pool or semaphore can also be used to achieve this.
+ *
+ * - Thread pool: Allocate a thread pool to handle these resource. When there is
+ * no more idle thread in the pool, the request is rejected without affecting
+ * other resources.
+ *
+ * - Semaphore: Use semaphore to control the concurrent count of the threads in
+ * this resource.
+ *
+ * The benefit of using thread pool is that, it can walk away gracefully when
+ * time out. But it also bring us the cost of context switch and additional
+ * threads. If the incoming requests is already served in a separated thread,
+ * for instance, a Servlet HTTP request, it will almost double the threads count if
+ * using thread pool.
+ *
+ *
Traffic Shaping
+ *
+ * When QPS exceeds the threshold, Sentinel will take actions to control the incoming request,
+ * and is configured by {@code controlBehavior} field in flow rules.
+ *
+ * If the load of system has been low for a while, and a large amount of
+ * requests comes, the system might not be able to handle all these requests at
+ * once. However if we steady increase the incoming request, the system can warm
+ * up and finally be able to handle all the requests.
+ * This warmup period can be configured by setting the field {@code warmUpPeriodSec} in flow rules.
+ *
+ * This strategy strictly controls the interval between requests.
+ * In other words, it allows requests to pass at a stable, uniform rate.
+ *
+ *
+ *
+ * This strategy is an implement of leaky bucket.
+ * It is used to handle the request at a stable rate and is often used in burst traffic (e.g. message handling).
+ * When a large number of requests beyond the system’s capacity arrive
+ * at the same time, the system using this strategy will handle requests and its
+ * fixed rate until all the requests have been processed or time out.
+ *
+ *
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+@Spi(order = Constants.ORDER_FLOW_SLOT)
+public class FlowSlot extends AbstractLinkedProcessorSlot {
+
+ private final FlowRuleChecker checker;
+
+ public FlowSlot() {
+ this(new FlowRuleChecker());
+ }
+
+ /**
+ * Package-private for test.
+ *
+ * @param checker flow rule checker
+ * @since 1.6.1
+ */
+ FlowSlot(FlowRuleChecker checker) {
+ AssertUtil.notNull(checker, "flow checker should not be null");
+ this.checker = checker;
+ }
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
+ boolean prioritized, Object... args) throws Throwable {
+ checkFlow(resourceWrapper, context, node, count, prioritized);
+
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+ }
+
+ void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized)
+ throws BlockException {
+ checker.checkFlow(ruleProvider, resource, context, node, count, prioritized);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ fireExit(context, resourceWrapper, count, args);
+ }
+
+ private final Function> ruleProvider = new Function>() {
+ @Override
+ public Collection apply(String resource) {
+ // Flow rule map should not be null.
+ Map> flowRules = FlowRuleManager.getFlowRuleMap();
+ return flowRules.get(resource);
+ }
+ };
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java
new file mode 100644
index 00000000..4ad3d482
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java
@@ -0,0 +1,40 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+/**
+ * An exception that marks previous prioritized request has been waiting till now, then should pass.
+ *
+ * @author jialiang.linjl
+ * @since 1.5.0
+ */
+public class PriorityWaitException extends RuntimeException {
+
+ private final long waitInMs;
+
+ public PriorityWaitException(long waitInMs) {
+ this.waitInMs = waitInMs;
+ }
+
+ public long getWaitInMs() {
+ return waitInMs;
+ }
+
+ @Override
+ public Throwable fillInStackTrace() {
+ return this;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java
new file mode 100755
index 00000000..8aeb474b
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java
@@ -0,0 +1,45 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+
+/**
+ * A universal interface for traffic shaping controller.
+ *
+ * @author jialiang.linjl
+ */
+public interface TrafficShapingController {
+
+ /**
+ * Check whether given resource entry can pass with provided count.
+ *
+ * @param node resource node
+ * @param acquireCount count to acquire
+ * @param prioritized whether the request is prioritized
+ * @return true if the resource entry can pass; false if it should be blocked
+ */
+ boolean canPass(Node node, int acquireCount, boolean prioritized);
+
+ /**
+ * Check whether given resource entry can pass with provided count.
+ *
+ * @param node resource node
+ * @param acquireCount count to acquire
+ * @return true if the resource entry can pass; false if it should be blocked
+ */
+ boolean canPass(Node node, int acquireCount);
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java
new file mode 100755
index 00000000..caebfeda
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.OccupyTimeoutProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+
+/**
+ * Default throttling controller (immediately reject strategy).
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class DefaultController implements TrafficShapingController {
+
+ private static final int DEFAULT_AVG_USED_TOKENS = 0;
+
+ private double count;
+ private int grade;
+
+ public DefaultController(double count, int grade) {
+ this.count = count;
+ this.grade = grade;
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount) {
+ return canPass(node, acquireCount, false);
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount, boolean prioritized) {
+ int curCount = avgUsedTokens(node);
+ if (curCount + acquireCount > count) {
+ if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) {
+ long currentTime;
+ long waitInMs;
+ currentTime = TimeUtil.currentTimeMillis();
+ waitInMs = node.tryOccupyNext(currentTime, acquireCount, count);
+ if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) {
+ node.addWaitingRequest(currentTime + waitInMs, acquireCount);
+ node.addOccupiedPass(acquireCount);
+ sleep(waitInMs);
+
+ // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}.
+ throw new PriorityWaitException(waitInMs);
+ }
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private int avgUsedTokens(Node node) {
+ if (node == null) {
+ return DEFAULT_AVG_USED_TOKENS;
+ }
+ return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps());
+ }
+
+ private void sleep(long timeMillis) {
+ try {
+ Thread.sleep(timeMillis);
+ } catch (InterruptedException e) {
+ // Ignore.
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java
new file mode 100755
index 00000000..c0cc4b99
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java
@@ -0,0 +1,93 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+
+/**
+ * @author jialiang.linjl
+ */
+public class RateLimiterController implements TrafficShapingController {
+
+ private final int maxQueueingTimeMs;
+ private final double count;
+
+ private final AtomicLong latestPassedTime = new AtomicLong(-1);
+
+ public RateLimiterController(int timeOut, double count) {
+ this.maxQueueingTimeMs = timeOut;
+ this.count = count;
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount) {
+ return canPass(node, acquireCount, false);
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount, boolean prioritized) {
+ // Pass when acquire count is less or equal than 0.
+ if (acquireCount <= 0) {
+ return true;
+ }
+ // Reject when count is less or equal than 0.
+ // Otherwise,the costTime will be max of long and waitTime will overflow in some cases.
+ if (count <= 0) {
+ return false;
+ }
+
+ long currentTime = TimeUtil.currentTimeMillis();
+ // Calculate the interval between every two requests.
+ long costTime = Math.round(1.0 * (acquireCount) / count * 1000);
+
+ // Expected pass time of this request.
+ long expectedTime = costTime + latestPassedTime.get();
+
+ if (expectedTime <= currentTime) {
+ // Contention may exist here, but it's okay.
+ latestPassedTime.set(currentTime);
+ return true;
+ } else {
+ // Calculate the time to wait.
+ long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis();
+ if (waitTime > maxQueueingTimeMs) {
+ return false;
+ } else {
+ long oldTime = latestPassedTime.addAndGet(costTime);
+ try {
+ waitTime = oldTime - TimeUtil.currentTimeMillis();
+ if (waitTime > maxQueueingTimeMs) {
+ latestPassedTime.addAndGet(-costTime);
+ return false;
+ }
+ // in race condition waitTime may <= 0
+ if (waitTime > 0) {
+ Thread.sleep(waitTime);
+ }
+ return true;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java
new file mode 100755
index 00000000..f28c7c63
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java
@@ -0,0 +1,177 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+
+/**
+ *
+ * The principle idea comes from Guava. However, the calculation of Guava is
+ * rate-based, which means that we need to translate rate to QPS.
+ *
+ *
+ *
+ * Requests arriving at the pulse may drag down long idle systems even though it
+ * has a much larger handling capability in stable period. It usually happens in
+ * scenarios that require extra time for initialization, e.g. DB establishes a connection,
+ * connects to a remote service, and so on. That’s why we need “warm up”.
+ *
+ *
+ *
+ * Sentinel's "warm-up" implementation is based on the Guava's algorithm.
+ * However, Guava’s implementation focuses on adjusting the request interval,
+ * which is similar to leaky bucket. Sentinel pays more attention to
+ * controlling the count of incoming requests per second without calculating its interval,
+ * which resembles token bucket algorithm.
+ *
+ *
+ *
+ * The remaining tokens in the bucket is used to measure the system utility.
+ * Suppose a system can handle b requests per second. Every second b tokens will
+ * be added into the bucket until the bucket is full. And when system processes
+ * a request, it takes a token from the bucket. The more tokens left in the
+ * bucket, the lower the utilization of the system; when the token in the token
+ * bucket is above a certain threshold, we call it in a "saturation" state.
+ *
+ *
+ *
+ * Base on Guava’s theory, there is a linear equation we can write this in the
+ * form y = m * x + b where y (a.k.a y(x)), or qps(q)), is our expected QPS
+ * given a saturated period (e.g. 3 minutes in), m is the rate of change from
+ * our cold (minimum) rate to our stable (maximum) rate, x (or q) is the
+ * occupied token.
+ *
+ *
+ * @author jialiang.linjl
+ */
+public class WarmUpController implements TrafficShapingController {
+
+ protected double count;
+ private int coldFactor;
+ protected int warningToken = 0;
+ private int maxToken;
+ protected double slope;
+
+ protected AtomicLong storedTokens = new AtomicLong(0);
+ protected AtomicLong lastFilledTime = new AtomicLong(0);
+
+ public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
+ construct(count, warmUpPeriodInSec, coldFactor);
+ }
+
+ public WarmUpController(double count, int warmUpPeriodInSec) {
+ construct(count, warmUpPeriodInSec, 3);
+ }
+
+ private void construct(double count, int warmUpPeriodInSec, int coldFactor) {
+
+ if (coldFactor <= 1) {
+ throw new IllegalArgumentException("Cold factor should be larger than 1");
+ }
+
+ this.count = count;
+
+ this.coldFactor = coldFactor;
+
+ // thresholdPermits = 0.5 * warmupPeriod / stableInterval.
+ // warningToken = 100;
+ warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1);
+ // / maxPermits = thresholdPermits + 2 * warmupPeriod /
+ // (stableInterval + coldInterval)
+ // maxToken = 200
+ maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor));
+
+ // slope
+ // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits
+ // - thresholdPermits);
+ slope = (coldFactor - 1.0) / count / (maxToken - warningToken);
+
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount) {
+ return canPass(node, acquireCount, false);
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount, boolean prioritized) {
+ long passQps = (long) node.passQps();
+
+ long previousQps = (long) node.previousPassQps();
+ syncToken(previousQps);
+
+ // 开始计算它的斜率
+ // 如果进入了警戒线,开始调整他的qps
+ long restToken = storedTokens.get();
+ if (restToken >= warningToken) {
+ long aboveToken = restToken - warningToken;
+ // 消耗的速度要比warning快,但是要比慢
+ // current interval = restToken*slope+1/count
+ double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
+ if (passQps + acquireCount <= warningQps) {
+ return true;
+ }
+ } else {
+ if (passQps + acquireCount <= count) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected void syncToken(long passQps) {
+ long currentTime = TimeUtil.currentTimeMillis();
+ currentTime = currentTime - currentTime % 1000;
+ long oldLastFillTime = lastFilledTime.get();
+ if (currentTime <= oldLastFillTime) {
+ return;
+ }
+
+ long oldValue = storedTokens.get();
+ long newValue = coolDownTokens(currentTime, passQps);
+
+ if (storedTokens.compareAndSet(oldValue, newValue)) {
+ long currentValue = storedTokens.addAndGet(0 - passQps);
+ if (currentValue < 0) {
+ storedTokens.set(0L);
+ }
+ lastFilledTime.set(currentTime);
+ }
+
+ }
+
+ private long coolDownTokens(long currentTime, long passQps) {
+ long oldValue = storedTokens.get();
+ long newValue = oldValue;
+
+ // 添加令牌的判断前提条件:
+ // 当令牌的消耗程度远远低于警戒线的时候
+ if (oldValue < warningToken) {
+ newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
+ } else if (oldValue > warningToken) {
+ if (passQps < (int)count / coldFactor) {
+ newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000);
+ }
+ }
+ return Math.min(newValue, maxToken);
+ }
+
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java
new file mode 100644
index 00000000..2420ab8f
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java
@@ -0,0 +1,88 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+
+/**
+ * @author jialiang.linjl
+ * @since 1.4.0
+ */
+public class WarmUpRateLimiterController extends WarmUpController {
+
+ private final int timeoutInMs;
+ private final AtomicLong latestPassedTime = new AtomicLong(-1);
+
+ public WarmUpRateLimiterController(double count, int warmUpPeriodSec, int timeOutMs, int coldFactor) {
+ super(count, warmUpPeriodSec, coldFactor);
+ this.timeoutInMs = timeOutMs;
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount) {
+ return canPass(node, acquireCount, false);
+ }
+
+ @Override
+ public boolean canPass(Node node, int acquireCount, boolean prioritized) {
+ long previousQps = (long) node.previousPassQps();
+ syncToken(previousQps);
+
+ long currentTime = TimeUtil.currentTimeMillis();
+
+ long restToken = storedTokens.get();
+ long costTime = 0;
+ long expectedTime = 0;
+ if (restToken >= warningToken) {
+ long aboveToken = restToken - warningToken;
+
+ // current interval = restToken*slope+1/count
+ double warmingQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count));
+ costTime = Math.round(1.0 * (acquireCount) / warmingQps * 1000);
+ } else {
+ costTime = Math.round(1.0 * (acquireCount) / count * 1000);
+ }
+ expectedTime = costTime + latestPassedTime.get();
+
+ if (expectedTime <= currentTime) {
+ latestPassedTime.set(currentTime);
+ return true;
+ } else {
+ long waitTime = costTime + latestPassedTime.get() - currentTime;
+ if (waitTime > timeoutInMs) {
+ return false;
+ } else {
+ long oldTime = latestPassedTime.addAndGet(costTime);
+ try {
+ waitTime = oldTime - TimeUtil.currentTimeMillis();
+ if (waitTime > timeoutInMs) {
+ latestPassedTime.addAndGet(-costTime);
+ return false;
+ }
+ if (waitTime > 0) {
+ Thread.sleep(waitTime);
+ }
+ return true;
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
new file mode 100755
index 00000000..e1fba5d3
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java
@@ -0,0 +1,165 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.SampleCountProperty;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+
+/**
+ *
+ * This slot maintains resource running statistics (response time, qps, thread
+ * count, exception), and a list of callers as well which is marked by
+ * {@link ContextUtil#enter(String origin)}
+ *
+ *
+ * One resource has only one cluster node, while one resource can have multiple
+ * default nodes.
+ *
+ * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share
+ * the same {@link ProcessorSlotChain} globally, no matter in which context. So if
+ * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)},
+ * the resource name must be same but context name may not.
+ *
+ *
+ * To get total statistics of the same resource in different context, same resource
+ * shares the same {@link ClusterNode} globally. All {@link ClusterNode}s are cached
+ * in this map.
+ *
+ *
+ * The longer the application runs, the more stable this mapping will
+ * become. so we don't concurrent map but a lock. as this lock only happens
+ * at the very beginning while concurrent map will hold the lock all the time.
+ *
+ */
+ private static volatile Map clusterNodeMap = new HashMap<>();
+
+ private static final Object lock = new Object();
+
+ private volatile ClusterNode clusterNode = null;
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
+ boolean prioritized, Object... args)
+ throws Throwable {
+ if (clusterNode == null) {
+ synchronized (lock) {
+ if (clusterNode == null) {
+ // Create the cluster node.
+ clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType());
+ HashMap newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16));
+ newMap.putAll(clusterNodeMap);
+ newMap.put(node.getId(), clusterNode);
+
+ clusterNodeMap = newMap;
+ }
+ }
+ }
+ node.setClusterNode(clusterNode);
+
+ /*
+ * if context origin is set, we should get or create a new {@link Node} of
+ * the specific origin.
+ */
+ if (!"".equals(context.getOrigin())) {
+ Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
+ context.getCurEntry().setOriginNode(originNode);
+ }
+
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ fireExit(context, resourceWrapper, count, args);
+ }
+
+ /**
+ * Get {@link ClusterNode} of the resource of the specific type.
+ *
+ * @param id resource name.
+ * @param type invoke type.
+ * @return the {@link ClusterNode}
+ */
+ public static ClusterNode getClusterNode(String id, EntryType type) {
+ return clusterNodeMap.get(new StringResourceWrapper(id, type));
+ }
+
+ /**
+ * Get {@link ClusterNode} of the resource name.
+ *
+ * @param id resource name.
+ * @return the {@link ClusterNode}.
+ */
+ public static ClusterNode getClusterNode(String id) {
+ if (id == null) {
+ return null;
+ }
+ ClusterNode clusterNode = null;
+
+ for (EntryType nodeType : EntryType.values()) {
+ clusterNode = clusterNodeMap.get(new StringResourceWrapper(id, nodeType));
+ if (clusterNode != null) {
+ break;
+ }
+ }
+
+ return clusterNode;
+ }
+
+ /**
+ * Get {@link ClusterNode}s map, this map holds all {@link ClusterNode}s, it's key is resource name,
+ * value is the related {@link ClusterNode}.
+ * DO NOT MODIFY the map returned.
+ *
+ * @return all {@link ClusterNode}s
+ */
+ public static Map getClusterNodeMap() {
+ return clusterNodeMap;
+ }
+
+ /**
+ * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL} or
+ * {@link SampleCountProperty#SAMPLE_COUNT} is changed.
+ */
+ public static void resetClusterNodes() {
+ for (ClusterNode node : clusterNodeMap.values()) {
+ node.reset();
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java
new file mode 100755
index 00000000..c7edf405
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEye;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogger;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil;
+
+public class EagleEyeLogUtil {
+
+ public static final String FILE_NAME = "sentinel-block.log";
+
+ private static StatLogger statLogger;
+
+ static {
+ String path = LogBase.getLogBaseDir() + FILE_NAME;
+
+ statLogger = EagleEye.statLoggerBuilder("sentinel-block-log")
+ .intervalSeconds(1)
+ .entryDelimiter('|')
+ .keyDelimiter(',')
+ .valueDelimiter(',')
+ .maxEntryCount(6000)
+ .configLogFilePath(path)
+ .maxFileSizeMB(300)
+ .maxBackupIndex(3)
+ .buildSingleton();
+ }
+
+ public static void log(String resource, String exceptionName, String ruleLimitApp, String origin, Long ruleId, int count) {
+ String ruleIdString = StringUtil.EMPTY;
+ if (ruleId != null) {
+ ruleIdString = String.valueOf(ruleId);
+ }
+ statLogger.stat(resource, exceptionName, ruleLimitApp, origin, ruleIdString).count(count);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java
new file mode 100755
index 00000000..35c1445a
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java
@@ -0,0 +1,58 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger.EagleEyeLogUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+
+/**
+ * A {@link com.alibaba.csp.sentinel.slotchain.ProcessorSlot} that is response for logging block exceptions
+ * to provide concrete logs for troubleshooting.
+ */
+@Spi(order = Constants.ORDER_LOG_SLOT)
+public class LogSlot extends AbstractLinkedProcessorSlot {
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ try {
+ fireEntry(context, resourceWrapper, obj, count, prioritized, args);
+ } catch (BlockException e) {
+ EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(),
+ context.getOrigin(), e.getRule().getId(), count);
+ throw e;
+ } catch (Throwable e) {
+ RecordLog.warn("Unexpected entry exception", e);
+ }
+
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ try {
+ fireExit(context, resourceWrapper, count, args);
+ } catch (Throwable e) {
+ RecordLog.warn("Unexpected entry exit exception", e);
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java
new file mode 100755
index 00000000..f79dee1a
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java
@@ -0,0 +1,181 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * This class will try to build the calling traces via
+ *
+ *
adding a new {@link DefaultNode} if needed as the last child in the context.
+ * The context's last node is the current node or the parent node of the context.
+ * Here the {@link EntranceNode} represents "entrance1" given by
+ * {@code ContextUtil.enter("entrance1", "appA")}.
+ *
+ *
+ * Both DefaultNode(nodeA) and ClusterNode(nodeA) holds statistics of "nodeA", which is given
+ * by {@code SphU.entry("nodeA")}
+ *
+ *
+ * The {@link ClusterNode} is uniquely identified by the ResourceId; the {@link DefaultNode}
+ * is identified by both the resource id and {@link Context}. In other words, one resource
+ * id will generate multiple {@link DefaultNode} for each distinct context, but only one
+ * {@link ClusterNode}.
+ *
+ *
+ * the following code shows one resource id in two different context:
+ *
+ * As we can see, two {@link DefaultNode} are created for "nodeA" in two context, but only one
+ * {@link ClusterNode} is created.
+ *
+ *
+ *
+ * We can also check this structure by calling:
+ * {@code curl http://localhost:8719/tree?type=root}
+ *
+ *
+ * @author jialiang.linjl
+ * @see EntranceNode
+ * @see ContextUtil
+ */
+@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
+public class NodeSelectorSlot extends AbstractLinkedProcessorSlot {
+
+ /**
+ * {@link DefaultNode}s of the same resource in different context.
+ */
+ private volatile Map map = new HashMap(10);
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
+ throws Throwable {
+ /*
+ * It's interesting that we use context name rather resource name as the map key.
+ *
+ * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share
+ * the same {@link ProcessorSlotChain} globally, no matter in which context. So if
+ * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, Object...)},
+ * the resource name must be same but context name may not.
+ *
+ * If we use {@link com.alibaba.csp.sentinel.SphU#entry(String resource)} to
+ * enter same resource in different context, using context name as map key can
+ * distinguish the same resource. In this case, multiple {@link DefaultNode}s will be created
+ * of the same resource name, for every distinct context (different context name) each.
+ *
+ * Consider another question. One resource may have multiple {@link DefaultNode},
+ * so what is the fastest way to get total statistics of the same resource?
+ * The answer is all {@link DefaultNode}s with same resource name share one
+ * {@link ClusterNode}. See {@link ClusterBuilderSlot} for detail.
+ */
+ DefaultNode node = map.get(context.getName());
+ if (node == null) {
+ synchronized (this) {
+ node = map.get(context.getName());
+ if (node == null) {
+ node = new DefaultNode(resourceWrapper, null);
+ HashMap cacheMap = new HashMap(map.size());
+ cacheMap.putAll(map);
+ cacheMap.put(context.getName(), node);
+ map = cacheMap;
+ // Build invocation tree
+ ((DefaultNode) context.getLastNode()).addChild(node);
+ }
+
+ }
+ }
+
+ context.setCurNode(node);
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ fireExit(context, resourceWrapper, count, args);
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java
new file mode 100644
index 00000000..42a9772d
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic;
+
+/**
+ * @author Eric Zhao
+ */
+public enum MetricEvent {
+
+ /**
+ * Normal pass.
+ */
+ PASS,
+ /**
+ * Normal block.
+ */
+ BLOCK,
+ EXCEPTION,
+ SUCCESS,
+ RT,
+
+ /**
+ * Passed in future quota (pre-occupied, since 1.5.0).
+ */
+ OCCUPIED_PASS
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java
new file mode 100755
index 00000000..a4e6adea
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java
@@ -0,0 +1,167 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic;
+
+import java.util.Collection;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException;
+
+/**
+ *
+ * A processor slot that dedicates to real time statistics.
+ * When entering this slot, we need to separately count the following
+ * information:
+ *
+ *
{@link ClusterNode}: total statistics of a cluster node of the resource ID.
+ *
Origin node: statistics of a cluster node from different callers/origins.
+ *
{@link DefaultNode}: statistics for specific resource name in the specific context.
+ *
Finally, the sum statistics of all entrances.
+ *
+ *
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+@Spi(order = Constants.ORDER_STATISTIC_SLOT)
+public class StatisticSlot extends AbstractLinkedProcessorSlot {
+
+ @Override
+ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
+ boolean prioritized, Object... args) throws Throwable {
+ try {
+ // Do some checking.
+ fireEntry(context, resourceWrapper, node, count, prioritized, args);
+
+ // Request passed, add thread count and pass count.
+ node.increaseThreadNum();
+ node.addPassRequest(count);
+
+ if (context.getCurEntry().getOriginNode() != null) {
+ // Add count for origin node.
+ context.getCurEntry().getOriginNode().increaseThreadNum();
+ context.getCurEntry().getOriginNode().addPassRequest(count);
+ }
+
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
+ // Add count for global inbound entry node for global statistics.
+ Constants.ENTRY_NODE.increaseThreadNum();
+ Constants.ENTRY_NODE.addPassRequest(count);
+ }
+
+ // Handle pass event with registered entry callback handlers.
+ for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
+ handler.onPass(context, resourceWrapper, node, count, args);
+ }
+ } catch (PriorityWaitException ex) {
+ node.increaseThreadNum();
+ if (context.getCurEntry().getOriginNode() != null) {
+ // Add count for origin node.
+ context.getCurEntry().getOriginNode().increaseThreadNum();
+ }
+
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
+ // Add count for global inbound entry node for global statistics.
+ Constants.ENTRY_NODE.increaseThreadNum();
+ }
+ // Handle pass event with registered entry callback handlers.
+ for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
+ handler.onPass(context, resourceWrapper, node, count, args);
+ }
+ } catch (BlockException e) {
+ // Blocked, set block exception to current entry.
+ context.getCurEntry().setBlockError(e);
+
+ // Add block count.
+ node.increaseBlockQps(count);
+ if (context.getCurEntry().getOriginNode() != null) {
+ context.getCurEntry().getOriginNode().increaseBlockQps(count);
+ }
+
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
+ // Add count for global inbound entry node for global statistics.
+ Constants.ENTRY_NODE.increaseBlockQps(count);
+ }
+
+ // Handle block event with registered entry callback handlers.
+ for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
+ handler.onBlocked(e, context, resourceWrapper, node, count, args);
+ }
+
+ throw e;
+ } catch (Throwable e) {
+ // Unexpected internal error, set error to current entry.
+ context.getCurEntry().setError(e);
+
+ throw e;
+ }
+ }
+
+ @Override
+ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
+ Node node = context.getCurNode();
+
+ if (context.getCurEntry().getBlockError() == null) {
+ // Calculate response time (use completeStatTime as the time of completion).
+ long completeStatTime = TimeUtil.currentTimeMillis();
+ context.getCurEntry().setCompleteTimestamp(completeStatTime);
+ long rt = completeStatTime - context.getCurEntry().getCreateTimestamp();
+
+ Throwable error = context.getCurEntry().getError();
+
+ // Record response time and success count.
+ recordCompleteFor(node, count, rt, error);
+ recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error);
+ if (resourceWrapper.getEntryType() == EntryType.IN) {
+ recordCompleteFor(Constants.ENTRY_NODE, count, rt, error);
+ }
+ }
+
+ // Handle exit event with registered exit callback handlers.
+ Collection exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks();
+ for (ProcessorSlotExitCallback handler : exitCallbacks) {
+ handler.onExit(context, resourceWrapper, count, args);
+ }
+
+ // fix bug https://github.com/alibaba/Sentinel/issues/2374
+ fireExit(context, resourceWrapper, count, args);
+ }
+
+ private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) {
+ if (node == null) {
+ return;
+ }
+ node.addRtAndSuccess(rt, batchCount);
+ node.decreaseThreadNum();
+
+ if (error != null && !(error instanceof BlockException)) {
+ node.increaseExceptionQps(batchCount);
+ }
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java
new file mode 100644
index 00000000..8092bf43
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback;
+
+/**
+ *
+ * Callback registry for {@link StatisticSlot}. Now two kind of callbacks are supported:
+ *
+ *
{@link ProcessorSlotEntryCallback}: callback for entry (passed and blocked)
+ *
{@link ProcessorSlotExitCallback}: callback for exiting {@link StatisticSlot}
+ *
+ *
+ *
+ * @author Eric Zhao
+ * @since 0.2.0
+ */
+public final class StatisticSlotCallbackRegistry {
+
+ private static final Map> entryCallbackMap
+ = new ConcurrentHashMap>();
+
+ private static final Map exitCallbackMap
+ = new ConcurrentHashMap();
+
+ public static void clearEntryCallback() {
+ entryCallbackMap.clear();
+ }
+
+ public static void clearExitCallback() {
+ exitCallbackMap.clear();
+ }
+
+ public static void addEntryCallback(String key, ProcessorSlotEntryCallback callback) {
+ entryCallbackMap.put(key, callback);
+ }
+
+ public static void addExitCallback(String key, ProcessorSlotExitCallback callback) {
+ exitCallbackMap.put(key, callback);
+ }
+
+ public static ProcessorSlotEntryCallback removeEntryCallback(String key) {
+ if (key == null) {
+ return null;
+ }
+ return entryCallbackMap.remove(key);
+ }
+
+ public static ProcessorSlotExitCallback removeExitCallback(String key) {
+ if (key == null) {
+ return null;
+ }
+ return exitCallbackMap.remove(key);
+ }
+
+ public static Collection> getEntryCallbacks() {
+ return entryCallbackMap.values();
+ }
+
+ public static Collection getExitCallbacks() {
+ return exitCallbackMap.values();
+ }
+
+ private StatisticSlotCallbackRegistry() {}
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
new file mode 100755
index 00000000..97f4e043
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java
@@ -0,0 +1,421 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil;
+
+/**
+ *
+ * Basic data structure for statistic metrics in Sentinel.
+ *
+ *
+ * Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span,
+ * and the total time span is {@link #intervalInMs}, so the total bucket amount is:
+ * {@code sampleCount = intervalInMs / windowLengthInMs}.
+ *
+ *
+ * @param type of statistic data
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ * @author Carpenter Lee
+ */
+public abstract class LeapArray {
+
+ protected int windowLengthInMs;
+ protected int sampleCount;
+ protected int intervalInMs;
+ private double intervalInSecond;
+
+ protected final AtomicReferenceArray> array;
+
+ /**
+ * The conditional (predicate) update lock is used only when current bucket is deprecated.
+ */
+ private final ReentrantLock updateLock = new ReentrantLock();
+
+ /**
+ * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}.
+ *
+ * @param sampleCount bucket count of the sliding window
+ * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds
+ */
+ public LeapArray(int sampleCount, int intervalInMs) {
+ AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
+ AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive");
+ AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided");
+
+ this.windowLengthInMs = intervalInMs / sampleCount;
+ this.intervalInMs = intervalInMs;
+ this.intervalInSecond = intervalInMs / 1000.0;
+ this.sampleCount = sampleCount;
+
+ this.array = new AtomicReferenceArray<>(sampleCount);
+ }
+
+ /**
+ * Get the bucket at current timestamp.
+ *
+ * @return the bucket at current timestamp
+ */
+ public WindowWrap currentWindow() {
+ return currentWindow(TimeUtil.currentTimeMillis());
+ }
+
+ /**
+ * Create a new statistic value for bucket.
+ *
+ * @param timeMillis current time in milliseconds
+ * @return the new empty bucket
+ */
+ public abstract T newEmptyBucket(long timeMillis);
+
+ /**
+ * Reset given bucket to provided start time and reset the value.
+ *
+ * @param startTime the start time of the bucket in milliseconds
+ * @param windowWrap current bucket
+ * @return new clean bucket at given start time
+ */
+ protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime);
+
+ private int calculateTimeIdx(/*@Valid*/ long timeMillis) {
+ long timeId = timeMillis / windowLengthInMs;
+ // Calculate current index so we can map the timestamp to the leap array.
+ return (int)(timeId % array.length());
+ }
+
+ protected long calculateWindowStart(/*@Valid*/ long timeMillis) {
+ return timeMillis - timeMillis % windowLengthInMs;
+ }
+
+ /**
+ * Get bucket item at provided timestamp.
+ *
+ * @param timeMillis a valid timestamp in milliseconds
+ * @return current bucket item at provided timestamp if the time is valid; null if time is invalid
+ */
+ public WindowWrap currentWindow(long timeMillis) {
+ if (timeMillis < 0) {
+ return null;
+ }
+
+ int idx = calculateTimeIdx(timeMillis);
+ // Calculate current bucket start time.
+ long windowStart = calculateWindowStart(timeMillis);
+
+ /*
+ * Get bucket item at given time from the array.
+ *
+ * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
+ * (2) Bucket is up-to-date, then just return the bucket.
+ * (3) Bucket is deprecated, then reset current bucket.
+ */
+ while (true) {
+ WindowWrap old = array.get(idx);
+ if (old == null) {
+ /*
+ * B0 B1 B2 NULL B4
+ * ||_______|_______|_______|_______|_______||___
+ * 200 400 600 800 1000 1200 timestamp
+ * ^
+ * time=888
+ * bucket is empty, so create new and update
+ *
+ * If the old bucket is absent, then we create a new bucket at {@code windowStart},
+ * then try to update circular array via a CAS operation. Only one thread can
+ * succeed to update, while other threads yield its time slice.
+ */
+ WindowWrap window = new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
+ if (array.compareAndSet(idx, null, window)) {
+ // Successfully updated, return the created bucket.
+ return window;
+ } else {
+ // Contention failed, the thread will yield its time slice to wait for bucket available.
+ Thread.yield();
+ }
+ } else if (windowStart == old.windowStart()) {
+ /*
+ * B0 B1 B2 B3 B4
+ * ||_______|_______|_______|_______|_______||___
+ * 200 400 600 800 1000 1200 timestamp
+ * ^
+ * time=888
+ * startTime of Bucket 3: 800, so it's up-to-date
+ *
+ * If current {@code windowStart} is equal to the start timestamp of old bucket,
+ * that means the time is within the bucket, so directly return the bucket.
+ */
+ return old;
+ } else if (windowStart > old.windowStart()) {
+ /*
+ * (old)
+ * B0 B1 B2 NULL B4
+ * |_______||_______|_______|_______|_______|_______||___
+ * ... 1200 1400 1600 1800 2000 2200 timestamp
+ * ^
+ * time=1676
+ * startTime of Bucket 2: 400, deprecated, should be reset
+ *
+ * If the start timestamp of old bucket is behind provided time, that means
+ * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}.
+ * Note that the reset and clean-up operations are hard to be atomic,
+ * so we need a update lock to guarantee the correctness of bucket update.
+ *
+ * The update lock is conditional (tiny scope) and will take effect only when
+ * bucket is deprecated, so in most cases it won't lead to performance loss.
+ */
+ if (updateLock.tryLock()) {
+ try {
+ // Successfully get the update lock, now we reset the bucket.
+ return resetWindowTo(old, windowStart);
+ } finally {
+ updateLock.unlock();
+ }
+ } else {
+ // Contention failed, the thread will yield its time slice to wait for bucket available.
+ Thread.yield();
+ }
+ } else if (windowStart < old.windowStart()) {
+ // Should not go through here, as the provided time is already behind.
+ return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
+ }
+ }
+ }
+
+ /**
+ * Get the previous bucket item before provided timestamp.
+ *
+ * @param timeMillis a valid timestamp in milliseconds
+ * @return the previous bucket item before provided timestamp
+ */
+ public WindowWrap getPreviousWindow(long timeMillis) {
+ if (timeMillis < 0) {
+ return null;
+ }
+ int idx = calculateTimeIdx(timeMillis - windowLengthInMs);
+ timeMillis = timeMillis - windowLengthInMs;
+ WindowWrap wrap = array.get(idx);
+
+ if (wrap == null || isWindowDeprecated(wrap)) {
+ return null;
+ }
+
+ if (wrap.windowStart() + windowLengthInMs < (timeMillis)) {
+ return null;
+ }
+
+ return wrap;
+ }
+
+ /**
+ * Get the previous bucket item for current timestamp.
+ *
+ * @return the previous bucket item for current timestamp
+ */
+ public WindowWrap getPreviousWindow() {
+ return getPreviousWindow(TimeUtil.currentTimeMillis());
+ }
+
+ /**
+ * Get statistic value from bucket for provided timestamp.
+ *
+ * @param timeMillis a valid timestamp in milliseconds
+ * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null
+ */
+ public T getWindowValue(long timeMillis) {
+ if (timeMillis < 0) {
+ return null;
+ }
+ int idx = calculateTimeIdx(timeMillis);
+
+ WindowWrap bucket = array.get(idx);
+
+ if (bucket == null || !bucket.isTimeInWindow(timeMillis)) {
+ return null;
+ }
+
+ return bucket.value();
+ }
+
+ /**
+ * Check if a bucket is deprecated, which means that the bucket
+ * has been behind for at least an entire window time span.
+ *
+ * @param windowWrap a non-null bucket
+ * @return true if the bucket is deprecated; otherwise false
+ */
+ public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) {
+ return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap);
+ }
+
+ public boolean isWindowDeprecated(long time, WindowWrap windowWrap) {
+ return time - windowWrap.windowStart() > intervalInMs;
+ }
+
+ /**
+ * Get valid bucket list for entire sliding window.
+ * The list will only contain "valid" buckets.
+ *
+ * @return valid bucket list for entire sliding window.
+ */
+ public List> list() {
+ return list(TimeUtil.currentTimeMillis());
+ }
+
+ public List> list(long validTime) {
+ int size = array.length();
+ List> result = new ArrayList>(size);
+
+ for (int i = 0; i < size; i++) {
+ WindowWrap windowWrap = array.get(i);
+ if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) {
+ continue;
+ }
+ result.add(windowWrap);
+ }
+
+ return result;
+ }
+
+ /**
+ * Get all buckets for entire sliding window including deprecated buckets.
+ *
+ * @return all buckets for entire sliding window
+ */
+ public List> listAll() {
+ int size = array.length();
+ List> result = new ArrayList>(size);
+
+ for (int i = 0; i < size; i++) {
+ WindowWrap windowWrap = array.get(i);
+ if (windowWrap == null) {
+ continue;
+ }
+ result.add(windowWrap);
+ }
+
+ return result;
+ }
+
+ /**
+ * Get aggregated value list for entire sliding window.
+ * The list will only contain value from "valid" buckets.
+ *
+ * @return aggregated value list for entire sliding window
+ */
+ public List values() {
+ return values(TimeUtil.currentTimeMillis());
+ }
+
+ public List values(long timeMillis) {
+ if (timeMillis < 0) {
+ return new ArrayList();
+ }
+ int size = array.length();
+ List result = new ArrayList(size);
+
+ for (int i = 0; i < size; i++) {
+ WindowWrap windowWrap = array.get(i);
+ if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
+ continue;
+ }
+ result.add(windowWrap.value());
+ }
+ return result;
+ }
+
+ /**
+ * Get the valid "head" bucket of the sliding window for provided timestamp.
+ * Package-private for test.
+ *
+ * @param timeMillis a valid timestamp in milliseconds
+ * @return the "head" bucket if it exists and is valid; otherwise null
+ */
+ WindowWrap getValidHead(long timeMillis) {
+ // Calculate index for expected head time.
+ int idx = calculateTimeIdx(timeMillis + windowLengthInMs);
+
+ WindowWrap wrap = array.get(idx);
+ if (wrap == null || isWindowDeprecated(wrap)) {
+ return null;
+ }
+
+ return wrap;
+ }
+
+ /**
+ * Get the valid "head" bucket of the sliding window at current timestamp.
+ *
+ * @return the "head" bucket if it exists and is valid; otherwise null
+ */
+ public WindowWrap getValidHead() {
+ return getValidHead(TimeUtil.currentTimeMillis());
+ }
+
+ /**
+ * Get sample count (total amount of buckets).
+ *
+ * @return sample count
+ */
+ public int getSampleCount() {
+ return sampleCount;
+ }
+
+ /**
+ * Get total interval length of the sliding window in milliseconds.
+ *
+ * @return interval in second
+ */
+ public int getIntervalInMs() {
+ return intervalInMs;
+ }
+
+ /**
+ * Get total interval length of the sliding window.
+ *
+ * @return interval in second
+ */
+ public double getIntervalInSecond() {
+ return intervalInSecond;
+ }
+
+ public void debug(long time) {
+ StringBuilder sb = new StringBuilder();
+ List> lists = list(time);
+ sb.append("Thread_").append(Thread.currentThread().getId()).append("_");
+ for (WindowWrap window : lists) {
+ sb.append(window.windowStart()).append(":").append(window.value().toString());
+ }
+ System.out.println(sb.toString());
+ }
+
+ public long currentWaiting() {
+ // TODO: default method. Should remove this later.
+ return 0;
+ }
+
+ public void addWaiting(long time, int acquireCount) {
+ // Do nothing by default.
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java
new file mode 100644
index 00000000..6f097d42
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java
@@ -0,0 +1,40 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base;
+
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * @author Eric Zhao
+ */
+public class UnaryLeapArray extends LeapArray {
+
+ public UnaryLeapArray(int sampleCount, int intervalInMs) {
+ super(sampleCount, intervalInMs);
+ }
+
+ @Override
+ public LongAdder newEmptyBucket(long time) {
+ return new LongAdder();
+ }
+
+ @Override
+ protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) {
+ windowWrap.resetTo(startTime);
+ windowWrap.value().reset();
+ return windowWrap;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
new file mode 100755
index 00000000..efe02b60
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java
@@ -0,0 +1,99 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base;
+
+/**
+ * Wrapper entity class for a period of time window.
+ *
+ * @param data type
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class WindowWrap {
+
+ /**
+ * Time length of a single window bucket in milliseconds.
+ */
+ private final long windowLengthInMs;
+
+ /**
+ * Start timestamp of the window in milliseconds.
+ */
+ private long windowStart;
+
+ /**
+ * Statistic data.
+ */
+ private T value;
+
+ /**
+ * @param windowLengthInMs a single window bucket's time length in milliseconds.
+ * @param windowStart the start timestamp of the window
+ * @param value statistic data
+ */
+ public WindowWrap(long windowLengthInMs, long windowStart, T value) {
+ this.windowLengthInMs = windowLengthInMs;
+ this.windowStart = windowStart;
+ this.value = value;
+ }
+
+ public long windowLength() {
+ return windowLengthInMs;
+ }
+
+ public long windowStart() {
+ return windowStart;
+ }
+
+ public T value() {
+ return value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ /**
+ * Reset start timestamp of current bucket to provided time.
+ *
+ * @param startTime valid start timestamp
+ * @return bucket after reset
+ */
+ public WindowWrap resetTo(long startTime) {
+ this.windowStart = startTime;
+ return this;
+ }
+
+ /**
+ * Check whether given timestamp is in current bucket.
+ *
+ * @param timeMillis valid timestamp in ms
+ * @return true if the given time is in current bucket, otherwise false
+ * @since 1.5.0
+ */
+ public boolean isTimeInWindow(long timeMillis) {
+ return windowStart <= timeMillis && timeMillis < windowStart + windowLengthInMs;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowWrap{" +
+ "windowLengthInMs=" + windowLengthInMs +
+ ", windowStart=" + windowStart +
+ ", value=" + value +
+ '}';
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java
new file mode 100755
index 00000000..5b7bcd78
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java
@@ -0,0 +1,139 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * Represents metrics data in a period of time span.
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class MetricBucket {
+
+ private final LongAdder[] counters;
+
+ private volatile long minRt;
+
+ public MetricBucket() {
+ MetricEvent[] events = MetricEvent.values();
+ this.counters = new LongAdder[events.length];
+ for (MetricEvent event : events) {
+ counters[event.ordinal()] = new LongAdder();
+ }
+ initMinRt();
+ }
+
+ public MetricBucket reset(MetricBucket bucket) {
+ for (MetricEvent event : MetricEvent.values()) {
+ counters[event.ordinal()].reset();
+ counters[event.ordinal()].add(bucket.get(event));
+ }
+ initMinRt();
+ return this;
+ }
+
+ private void initMinRt() {
+ this.minRt = SentinelConfig.statisticMaxRt();
+ }
+
+ /**
+ * Reset the adders.
+ *
+ * @return new metric bucket in initial state
+ */
+ public MetricBucket reset() {
+ for (MetricEvent event : MetricEvent.values()) {
+ counters[event.ordinal()].reset();
+ }
+ initMinRt();
+ return this;
+ }
+
+ public long get(MetricEvent event) {
+ return counters[event.ordinal()].sum();
+ }
+
+ public MetricBucket add(MetricEvent event, long n) {
+ counters[event.ordinal()].add(n);
+ return this;
+ }
+
+ public long pass() {
+ return get(MetricEvent.PASS);
+ }
+
+ public long occupiedPass() {
+ return get(MetricEvent.OCCUPIED_PASS);
+ }
+
+ public long block() {
+ return get(MetricEvent.BLOCK);
+ }
+
+ public long exception() {
+ return get(MetricEvent.EXCEPTION);
+ }
+
+ public long rt() {
+ return get(MetricEvent.RT);
+ }
+
+ public long minRt() {
+ return minRt;
+ }
+
+ public long success() {
+ return get(MetricEvent.SUCCESS);
+ }
+
+ public void addPass(int n) {
+ add(MetricEvent.PASS, n);
+ }
+
+ public void addOccupiedPass(int n) {
+ add(MetricEvent.OCCUPIED_PASS, n);
+ }
+
+ public void addException(int n) {
+ add(MetricEvent.EXCEPTION, n);
+ }
+
+ public void addBlock(int n) {
+ add(MetricEvent.BLOCK, n);
+ }
+
+ public void addSuccess(int n) {
+ add(MetricEvent.SUCCESS, n);
+ }
+
+ public void addRT(long rt) {
+ add(MetricEvent.RT, rt);
+
+ // Not thread-safe, but it's okay.
+ if (rt < minRt) {
+ minRt = rt;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "p: " + pass() + ", b: " + block() + ", w: " + occupiedPass();
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
new file mode 100755
index 00000000..40131061
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java
@@ -0,0 +1,338 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.MetricEvent;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.BucketLeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate;
+
+/**
+ * The basic metric class in Sentinel using a {@link BucketLeapArray} internal.
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public class ArrayMetric implements Metric {
+
+ private final LeapArray data;
+
+ public ArrayMetric(int sampleCount, int intervalInMs) {
+ this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
+ }
+
+ public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) {
+ if (enableOccupy) {
+ this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs);
+ } else {
+ this.data = new BucketLeapArray(sampleCount, intervalInMs);
+ }
+ }
+
+ /**
+ * For unit test.
+ */
+ public ArrayMetric(LeapArray array) {
+ this.data = array;
+ }
+
+ @Override
+ public long success() {
+ data.currentWindow();
+ long success = 0;
+
+ List list = data.values();
+ for (MetricBucket window : list) {
+ success += window.success();
+ }
+ return success;
+ }
+
+ @Override
+ public long maxSuccess() {
+ data.currentWindow();
+ long success = 0;
+
+ List list = data.values();
+ for (MetricBucket window : list) {
+ if (window.success() > success) {
+ success = window.success();
+ }
+ }
+ return Math.max(success, 1);
+ }
+
+ @Override
+ public long exception() {
+ data.currentWindow();
+ long exception = 0;
+ List list = data.values();
+ for (MetricBucket window : list) {
+ exception += window.exception();
+ }
+ return exception;
+ }
+
+ @Override
+ public long block() {
+ data.currentWindow();
+ long block = 0;
+ List list = data.values();
+ for (MetricBucket window : list) {
+ block += window.block();
+ }
+ return block;
+ }
+
+ @Override
+ public long pass() {
+ data.currentWindow();
+ long pass = 0;
+ List list = data.values();
+
+ for (MetricBucket window : list) {
+ pass += window.pass();
+ }
+ return pass;
+ }
+
+ @Override
+ public long occupiedPass() {
+ data.currentWindow();
+ long pass = 0;
+ List list = data.values();
+ for (MetricBucket window : list) {
+ pass += window.occupiedPass();
+ }
+ return pass;
+ }
+
+ @Override
+ public long rt() {
+ data.currentWindow();
+ long rt = 0;
+ List list = data.values();
+ for (MetricBucket window : list) {
+ rt += window.rt();
+ }
+ return rt;
+ }
+
+ @Override
+ public long minRt() {
+ data.currentWindow();
+ long rt = SentinelConfig.statisticMaxRt();
+ List list = data.values();
+ for (MetricBucket window : list) {
+ if (window.minRt() < rt) {
+ rt = window.minRt();
+ }
+ }
+
+ return Math.max(1, rt);
+ }
+
+ @Override
+ public List details() {
+ List details = new ArrayList<>();
+ data.currentWindow();
+ List> list = data.list();
+ for (WindowWrap window : list) {
+ if (window == null) {
+ continue;
+ }
+
+ details.add(fromBucket(window));
+ }
+
+ return details;
+ }
+
+ @Override
+ public List detailsOnCondition(Predicate timePredicate) {
+ List details = new ArrayList<>();
+ data.currentWindow();
+ List> list = data.list();
+ for (WindowWrap window : list) {
+ if (window == null) {
+ continue;
+ }
+ if (timePredicate != null && !timePredicate.test(window.windowStart())) {
+ continue;
+ }
+
+ details.add(fromBucket(window));
+ }
+
+ return details;
+ }
+
+ private MetricNode fromBucket(WindowWrap wrap) {
+ MetricNode node = new MetricNode();
+ node.setBlockQps(wrap.value().block());
+ node.setExceptionQps(wrap.value().exception());
+ node.setPassQps(wrap.value().pass());
+ long successQps = wrap.value().success();
+ node.setSuccessQps(successQps);
+ if (successQps != 0) {
+ node.setRt(wrap.value().rt() / successQps);
+ } else {
+ node.setRt(wrap.value().rt());
+ }
+ node.setTimestamp(wrap.windowStart());
+ node.setOccupiedPassQps(wrap.value().occupiedPass());
+ return node;
+ }
+
+ @Override
+ public MetricBucket[] windows() {
+ data.currentWindow();
+ return data.values().toArray(new MetricBucket[0]);
+ }
+
+ @Override
+ public void addException(int count) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addException(count);
+ }
+
+ @Override
+ public void addBlock(int count) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addBlock(count);
+ }
+
+ @Override
+ public void addWaiting(long time, int acquireCount) {
+ data.addWaiting(time, acquireCount);
+ }
+
+ @Override
+ public void addOccupiedPass(int acquireCount) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addOccupiedPass(acquireCount);
+ }
+
+ @Override
+ public void addSuccess(int count) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addSuccess(count);
+ }
+
+ @Override
+ public void addPass(int count) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addPass(count);
+ }
+
+ @Override
+ public void addRT(long rt) {
+ WindowWrap wrap = data.currentWindow();
+ wrap.value().addRT(rt);
+ }
+
+ @Override
+ public void debug() {
+ data.debug(System.currentTimeMillis());
+ }
+
+ @Override
+ public long previousWindowBlock() {
+ data.currentWindow();
+ WindowWrap wrap = data.getPreviousWindow();
+ if (wrap == null) {
+ return 0;
+ }
+ return wrap.value().block();
+ }
+
+ @Override
+ public long previousWindowPass() {
+ data.currentWindow();
+ WindowWrap wrap = data.getPreviousWindow();
+ if (wrap == null) {
+ return 0;
+ }
+ return wrap.value().pass();
+ }
+
+ public void add(MetricEvent event, long count) {
+ data.currentWindow().value().add(event, count);
+ }
+
+ public long getCurrentCount(MetricEvent event) {
+ return data.currentWindow().value().get(event);
+ }
+
+ /**
+ * Get total sum for provided event in {@code intervalInSec}.
+ *
+ * @param event event to calculate
+ * @return total sum for event
+ */
+ public long getSum(MetricEvent event) {
+ data.currentWindow();
+ long sum = 0;
+
+ List buckets = data.values();
+ for (MetricBucket bucket : buckets) {
+ sum += bucket.get(event);
+ }
+ return sum;
+ }
+
+ /**
+ * Get average count for provided event per second.
+ *
+ * @param event event to calculate
+ * @return average count per second for event
+ */
+ public double getAvg(MetricEvent event) {
+ return getSum(event) / data.getIntervalInSecond();
+ }
+
+ @Override
+ public long getWindowPass(long timeMillis) {
+ MetricBucket bucket = data.getWindowValue(timeMillis);
+ if (bucket == null) {
+ return 0L;
+ }
+ return bucket.pass();
+ }
+
+ @Override
+ public long waiting() {
+ return data.currentWaiting();
+ }
+
+ @Override
+ public double getWindowIntervalInSec() {
+ return data.getIntervalInSecond();
+ }
+
+ @Override
+ public int getSampleCount() {
+ return data.getSampleCount();
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java
new file mode 100755
index 00000000..07cc55fc
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+
+/**
+ * The fundamental data structure for metric statistics in a time span.
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ * @see LeapArray
+ */
+public class BucketLeapArray extends LeapArray {
+
+ public BucketLeapArray(int sampleCount, int intervalInMs) {
+ super(sampleCount, intervalInMs);
+ }
+
+ @Override
+ public MetricBucket newEmptyBucket(long time) {
+ return new MetricBucket();
+ }
+
+ @Override
+ protected WindowWrap resetWindowTo(WindowWrap w, long startTime) {
+ // Update the start time and reset value.
+ w.resetTo(startTime);
+ w.value().reset();
+ return w;
+ }
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java
new file mode 100644
index 00000000..1ef31252
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 1999-2019 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric;
+
+/**
+ * @author Eric Zhao
+ * @since 1.5.0
+ */
+public interface DebugSupport {
+
+ /**
+ * For debug;
+ */
+ void debug();
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
new file mode 100755
index 00000000..3d771394
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java
@@ -0,0 +1,203 @@
+/*
+ * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric;
+
+import java.util.List;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate;
+
+/**
+ * Represents a basic structure recording invocation metrics of protected resources.
+ *
+ * @author jialiang.linjl
+ * @author Eric Zhao
+ */
+public interface Metric extends DebugSupport {
+
+ /**
+ * Get total success count.
+ *
+ * @return success count
+ */
+ long success();
+
+ /**
+ * Get max success count.
+ *
+ * @return max success count
+ */
+ long maxSuccess();
+
+ /**
+ * Get total exception count.
+ *
+ * @return exception count
+ */
+ long exception();
+
+ /**
+ * Get total block count.
+ *
+ * @return block count
+ */
+ long block();
+
+ /**
+ * Get total pass count. not include {@link #occupiedPass()}
+ *
+ * @return pass count
+ */
+ long pass();
+
+ /**
+ * Get total response time.
+ *
+ * @return total RT
+ */
+ long rt();
+
+ /**
+ * Get the minimal RT.
+ *
+ * @return minimal RT
+ */
+ long minRt();
+
+ /**
+ * Get aggregated metric nodes of all resources.
+ *
+ * @return metric node list of all resources
+ */
+ List details();
+
+ /**
+ * Generate aggregated metric items that satisfies the time predicate.
+ *
+ * @param timePredicate time predicate
+ * @return aggregated metric items
+ * @since 1.7.0
+ */
+ List detailsOnCondition(Predicate timePredicate);
+
+ /**
+ * Get the raw window array.
+ *
+ * @return window metric array
+ */
+ MetricBucket[] windows();
+
+ /**
+ * Add current exception count.
+ *
+ * @param n count to add
+ */
+ void addException(int n);
+
+ /**
+ * Add current block count.
+ *
+ * @param n count to add
+ */
+ void addBlock(int n);
+
+ /**
+ * Add current completed count.
+ *
+ * @param n count to add
+ */
+ void addSuccess(int n);
+
+ /**
+ * Add current pass count.
+ *
+ * @param n count to add
+ */
+ void addPass(int n);
+
+ /**
+ * Add given RT to current total RT.
+ *
+ * @param rt RT
+ */
+ void addRT(long rt);
+
+ /**
+ * Get the sliding window length in seconds.
+ *
+ * @return the sliding window length
+ */
+ double getWindowIntervalInSec();
+
+ /**
+ * Get sample count of the sliding window.
+ *
+ * @return sample count of the sliding window.
+ */
+ int getSampleCount();
+
+ /**
+ * Note: this operation will not perform refreshing, so will not generate new buckets.
+ *
+ * @param timeMillis valid time in ms
+ * @return pass count of the bucket exactly associated to provided timestamp, or 0 if the timestamp is invalid
+ * @since 1.5.0
+ */
+ long getWindowPass(long timeMillis);
+
+ // Occupy-based (@since 1.5.0)
+
+ /**
+ * Add occupied pass, which represents pass requests that borrow the latter windows' token.
+ *
+ * @param acquireCount tokens count.
+ * @since 1.5.0
+ */
+ void addOccupiedPass(int acquireCount);
+
+ /**
+ * Add request that occupied.
+ *
+ * @param futureTime future timestamp that the acquireCount should be added on.
+ * @param acquireCount tokens count.
+ * @since 1.5.0
+ */
+ void addWaiting(long futureTime, int acquireCount);
+
+ /**
+ * Get waiting pass account
+ *
+ * @return waiting pass count
+ * @since 1.5.0
+ */
+ long waiting();
+
+ /**
+ * Get occupied pass count.
+ *
+ * @return occupied pass count
+ * @since 1.5.0
+ */
+ long occupiedPass();
+
+ // Tool methods.
+
+ long previousWindowBlock();
+
+ long previousWindowPass();
+}
diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java
new file mode 100644
index 00000000..a4d7965f
--- /dev/null
+++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.occupy;
+
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
+import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
+
+/**
+ * A kind of {@code BucketLeapArray} that only reserves for future buckets.
+ *
+ * @author jialiang.linjl
+ * @since 1.5.0
+ */
+public class FutureBucketLeapArray extends LeapArray