Repository: alibaba/Sentinel
Pull request #3234 (2023-09-29 09:36:20)

feature: Adaptive Flow Control
<!--  Thanks for submitting a pull request! Here are some tips for you:
1. Please make sure you have read and understood the contributing guidelines: https://github.com/alibaba/Sentinel/blob/master/CONTRIBUTING.md
2. Please make sure the PR has a corresponding issue.
-->

### Describe what this PR does / why we need it

传统的流量防护体系要求用户手动配置规则，且规则策略需要用户指定，这在很多突发稳定性风险的场景下可能比较难。Sentinel 在这一块也进行过一些自适应流控的尝试，包括自适应系统过载保护以及基于 Q-Learning 的自适应保护等；本项目期望扩展目前 Sentinel 现有自适应机制，结合 OpenSergo 控制面与决策中心，结合控制论、排队论等理论，探索效果更优、覆盖更多场景的自适应流控实现，帮助用户自动化保障服务稳定性。

基于 The little's law、TCP BBR 等策略实现 Sentinel 接口级与应用级自适应并发控制，并给出各流量场景下的验证对比文档；结合 Google SRE book 中的策略，补充 Sentinel 熔断策略实现，并给出各场景下的验证对比文档；尝试基于现有服务自愈的方法论（排队论、控制论）抽出自适应流控的标准接口。

### Does this pull request fix one issue?

#3010 #1845

### Describe how you did it

在Sentinel的责任链中新增加一个 AdaptiveSlot，让这个 slot 负责自适应修改限流阈值。在 AdaptiveSlot 中通过调用 AdaptiveRuleManager.adaptiveLimit 增加自适应流控，其中主要流程是计数，当超过一定次数时触发阈值调整部分。
在 AdaptiveSlot 中通过调用 AdaptiveRuleManager.adaptiveLimit 增加自适应流控，其中主要流程是计数，当超过一定次数时触发阈值调整部分。

除了上述统计调用次数启动自适应流控的方法，还有一个后台运行的线程，每隔固定时间调用调整阈值的代码。这样做的好处是，防止出现刚启动时阈值设置得比较小，有很多流量浪费的问题，第一种方法使得在 qps 较大时更新得更快速些。adaptiveLimit 方法中通过 node 获取到 minRT，rt，QPS 等数据，还额外存储了过去一段时间内的自适应限流阈值数据，将这些数据通过具体的 Limiter 进行计算。每个 Limiter 都实现了 AbstractLimit 类的 update 方法，在该方法中通过 minRT，rt，QPS 和过去一段时间的限流数据进行当前的阈值调整。

Vegas是一种主动调整cwnd的拥塞控制算法，根据RTT来判断网络是否出现拥塞，把网络中出现的最小RTT作为无拥塞情况的RTT，用该RTT计算期望吞吐率，与实际的吞吐率作差，当差值较小时增大窗口，差值较大时减少窗口。Vegas 适合微调，其可靠性较好；Gradient限流算法通过计算无负载时的 RTT 与当前 RTT 的比值来判断是否出现排队情况，他的改进算法又通过平均值计算梯度，该算法适合快速确定阈值大小，稳定性较好，适用范围较广；BRPC算法自适应限流会不断地对请求进行采样，当采样窗口的样本数量足够时，会根据样本的平均延迟和服务当前的 qps 计算出下一个采样窗口的 max_concurrency 。 在我的实现中 BRPC 算法效果并不稳定，出现了非常明显的波动现象，BRPC 算法更适合在各种值都能精确测量的情况，并且这可能会付出一些开销。

Describe how to verify it

可以运行AdaptiveDemo，通过调整initAdaptiveRule中的参数改变自适应限流配置，通过打印或日志查看限流效果。

Special notes for reviews



LearningGp: Thanks for contributing!

Patch:
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
index 5dc23568..937fd579 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java
@@ -79,6 +79,7 @@ public final class Constants {
     public static final int ORDER_STATISTIC_SLOT = -7000;
     public static final int ORDER_AUTHORITY_SLOT = -6000;
     public static final int ORDER_SYSTEM_SLOT = -5000;
+    public static final int ORDER_ADAPTIVE_SLOT = -4000;
     public static final int ORDER_FLOW_SLOT = -2000;
     public static final int ORDER_DEFAULT_CIRCUIT_BREAKER_SLOT = -1500;
     public static final int ORDER_DEGRADE_SLOT = -1000;
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveLimiter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveLimiter.java
new file mode 100644
index 00000000..2e90e8a7
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveLimiter.java
@@ -0,0 +1,53 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.node.ClusterNode;
+import com.alibaba.csp.sentinel.node.StatisticNode;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
+import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveLimiter
+ * @date 2023/8/7 14:14
+ */
+public class AdaptiveLimiter {
+    public static void adaptiveLimit(AdaptiveRule rule) {
+        ClusterNode node = ClusterBuilderSlot.getClusterNode(rule.getResource(), EntryType.IN);
+        if (node != null) {
+            adaptiveLimit(rule, node);
+        }
+    }
+
+    public static void adaptiveLimit(AdaptiveRule rule, StatisticNode node) {
+        double minRt = node.minRt();
+        double rt = node.avgRt();
+        if (rt == 0 || minRt == 1) {
+            return;
+        }
+        int newLimit = rule.getLimiter().update(rule, node);
+        rule.setCount(newLimit);
+        updateFlowQpsRule(rule.getFlowId(), rule.getResource(), newLimit);
+        rule.addCount(newLimit);
+        rule.setTimes(0);
+    }
+
+    private static void updateFlowQpsRule(long flowId, String key, int newLimit) {
+        FlowRule updateFlow = new FlowRule();
+        updateFlow.setId(flowId);
+        updateFlow.setResource(key);
+        updateFlow.setCount(newLimit);
+        updateFlow.setGrade(RuleConstant.FLOW_GRADE_QPS);
+        updateFlow.setLimitApp("default");
+        List<FlowRule> updateFlows = new ArrayList<>();
+        updateFlows.add(updateFlow);
+        FlowRuleManager.loadRules(updateFlows);
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRule.java
new file mode 100644
index 00000000..5c95d33d
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRule.java
@@ -0,0 +1,88 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.AbstractLimit;
+import com.alibaba.csp.sentinel.slots.block.AbstractRule;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveRule
+ * @date 2023/8/2 13:35
+ */
+public class AdaptiveRule extends AbstractRule {
+    private int strategy;
+
+    private long flowId;
+
+    public long getFlowId() {
+        return flowId;
+    }
+
+    public AdaptiveRule setFlowId(int flowId) {
+        this.flowId = flowId;
+        return this;
+    }
+
+    private int count;
+
+    public int getStrategy() {
+        return strategy;
+    }
+
+    public AdaptiveRule setStrategy(int strategy) {
+        this.strategy = strategy;
+        return this;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public AdaptiveRule setCount(int count) {
+        this.count = count;
+        return this;
+    }
+
+    private AbstractLimit limiter;
+
+    private Queue<Integer> oldCounts = new ConcurrentLinkedQueue<>();
+
+    private final int oldCountsMaxSize = RuleConstant.OLD_COUNTS_MAX_SIZE;
+
+    private AtomicInteger times = new AtomicInteger(0);
+
+    public AbstractLimit getLimiter() {
+        return limiter;
+    }
+
+    public AdaptiveRule setLimiter(AbstractLimit limiter) {
+        this.limiter = limiter;
+        return this;
+    }
+
+    public boolean addCount(int count) {
+        while (oldCounts.size() >= oldCountsMaxSize) {
+            oldCounts.poll();
+        }
+        return oldCounts.add(count);
+    }
+
+    public Queue<Integer> getOldCounts() {
+        return oldCounts;
+    }
+
+    public int incrementTimes() {
+        return times.incrementAndGet();
+    }
+
+    AdaptiveRule setTimes(int times) {
+        this.times.set(times);
+        return this;
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleManager.java
new file mode 100644
index 00000000..77eec0d9
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleManager.java
@@ -0,0 +1,92 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.node.DefaultNode;
+import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
+import com.alibaba.csp.sentinel.property.PropertyListener;
+import com.alibaba.csp.sentinel.property.SentinelProperty;
+import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveRuleManager
+ * @date 2023/8/2 14:08
+ */
+public class AdaptiveRuleManager {
+    private static volatile Map<String, AdaptiveRule> adaptiveRules = new ConcurrentHashMap<>();
+
+    private static final AdaptivePropertyListener LISTENER = new AdaptivePropertyListener();
+
+    private static SentinelProperty<List<AdaptiveRule>> currentProperty = new DynamicSentinelProperty<>();
+
+    private static AdaptiveTask adaptiveTask = null;
+
+    private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1,
+            new NamedThreadFactory("sentinel-adaptive-limiter-task", true));
+
+    static {
+        adaptiveTask = new AdaptiveTask();
+        scheduler.scheduleAtFixedRate(adaptiveTask, 0, 1, TimeUnit.SECONDS);
+        currentProperty.addListener(LISTENER);
+    }
+
+    public static Map<String, AdaptiveRule> getAdaptiveRules() {
+        return adaptiveRules;
+    }
+
+    public static void adaptiveLimit(ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized) {
+        if (resourceWrapper == null || resourceWrapper.getName() == null || !adaptiveRules.containsKey(resourceWrapper.getName())) {
+            return;
+        }
+        if (resourceWrapper.getEntryType() != EntryType.IN) {
+            return;
+        }
+
+        AdaptiveRule adaptiveRule = adaptiveRules.get(resourceWrapper.getName());
+        int times = adaptiveRule.incrementTimes();
+        if (times > RuleConstant.ADAPTIVE_LIMIT_THRESHOLD) {
+            AdaptiveLimiter.adaptiveLimit(adaptiveRule, node);
+        }
+    }
+
+    public static void loadRules(List<AdaptiveRule> rules) {
+        for (AdaptiveRule rule : rules) {
+            adaptiveRules.put(rule.getResource(), rule);
+        }
+    }
+
+
+    private static final class AdaptivePropertyListener implements PropertyListener<List<AdaptiveRule>> {
+
+        @Override
+        public void configUpdate(List<AdaptiveRule> value) {
+            if (value != null) {
+                adaptiveRules = AdaptiveRuleUtil.buildAdaptiveRuleMap(value);
+                RecordLog.info("[AdaptiveRuleManager] Adaptive rule updated: {}", value);
+            } else {
+                RecordLog.info("[AdaptiveRuleManager] Adaptive rule update failed");
+            }
+        }
+
+        @Override
+        public void configLoad(List<AdaptiveRule> value) {
+            if (value != null) {
+                adaptiveRules = AdaptiveRuleUtil.buildAdaptiveRuleMap(value);
+                RecordLog.info("[AdaptiveRuleManager] Adaptive rule loaded: {}", value);
+            } else {
+                RecordLog.info("[AdaptiveRuleManager] Adaptive rule load failed");
+            }
+        }
+    }
+
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleUtil.java
new file mode 100644
index 00000000..248d55ff
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveRuleUtil.java
@@ -0,0 +1,67 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.AbstractLimit;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.BRPCLimit;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.GradientLimit;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.VegasLimit;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveRuleUtil
+ * @date 2023/8/5 16:09
+ */
+public class AdaptiveRuleUtil {
+    public static Map<String, AdaptiveRule> buildAdaptiveRuleMap(List<AdaptiveRule> list) {
+        Map<String, AdaptiveRule> newRuleMap = new ConcurrentHashMap<>();
+        if (list == null || list.isEmpty()) {
+            return newRuleMap;
+        }
+        for (AdaptiveRule rule : list) {
+            if (!checkIntegrity(rule)) {
+                continue;
+            }
+            AbstractLimit limiter = generateRater(rule);
+            rule.setLimiter(limiter);
+
+            String key = rule.getResource();
+            newRuleMap.put(key, rule);
+        }
+        return newRuleMap;
+    }
+
+    private static boolean checkIntegrity(AdaptiveRule rule) {
+        if (rule == null) {
+            return false;
+        }
+        Integer[] arr = {RuleConstant.ADAPTIVE_VEGAS,
+                RuleConstant.ADAPTIVE_GRADIENT,
+                RuleConstant.ADAPTIVE_BRPC};
+        List<Integer> list = Arrays.asList(arr);
+        if (!list.contains(rule.getStrategy())) {
+            return false;
+        }
+        return true;
+    }
+
+    private static AbstractLimit generateRater(AdaptiveRule rule) {
+        switch (rule.getStrategy()) {
+            case RuleConstant.ADAPTIVE_GRADIENT:
+                return GradientLimit.getInstance();
+            case RuleConstant.ADAPTIVE_BRPC:
+                return BRPCLimit.getInstance();
+            case RuleConstant.ADAPTIVE_VEGAS:
+            default:
+                return VegasLimit.getInstance();
+        }
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveSlot.java
new file mode 100644
index 00000000..b794f7bd
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveSlot.java
@@ -0,0 +1,31 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+import com.alibaba.csp.sentinel.Constants;
+import com.alibaba.csp.sentinel.context.Context;
+import com.alibaba.csp.sentinel.node.DefaultNode;
+import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
+import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
+import com.alibaba.csp.sentinel.spi.Spi;
+
+/**
+ * Adaptive Flow Control and Flow Limiting
+ *
+ * @author ElonTusk
+ * @name AdaptiveSlot
+ * @date 2023/8/2 13:12
+ */
+@Spi(order = Constants.ORDER_ADAPTIVE_SLOT)
+public class AdaptiveSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
+    @Override
+    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
+                      boolean prioritized, Object... args) throws Throwable {
+        AdaptiveRuleManager.adaptiveLimit(resourceWrapper, node, count, prioritized);
+        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/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveTask.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveTask.java
new file mode 100644
index 00000000..65373100
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/AdaptiveTask.java
@@ -0,0 +1,19 @@
+package com.alibaba.csp.sentinel.slots.adaptive;
+
+import java.util.Map;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveListener
+ * @date 2023/8/3 13:24
+ */
+public class AdaptiveTask implements Runnable {
+
+    @Override
+    public void run() {
+        Map<String, AdaptiveRule> adaptiveRules = AdaptiveRuleManager.getAdaptiveRules();
+        for (AdaptiveRule rule : adaptiveRules.values()) {
+            AdaptiveLimiter.adaptiveLimit(rule);
+        }
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/AbstractLimit.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/AbstractLimit.java
new file mode 100644
index 00000000..7d3c01c4
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/AbstractLimit.java
@@ -0,0 +1,15 @@
+package com.alibaba.csp.sentinel.slots.adaptive.algorithm;
+
+import com.alibaba.csp.sentinel.node.StatisticNode;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRule;
+
+import java.util.Queue;
+
+/**
+ * @author ElonTusk
+ * @name AbstractLimit
+ * @date 2023/8/2 14:48
+ */
+public abstract class AbstractLimit {
+    public abstract int update(AdaptiveRule rule, StatisticNode node);
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/BRPCLimit.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/BRPCLimit.java
new file mode 100644
index 00000000..49017176
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/BRPCLimit.java
@@ -0,0 +1,58 @@
+package com.alibaba.csp.sentinel.slots.adaptive.algorithm;
+
+import com.alibaba.csp.sentinel.node.StatisticNode;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRule;
+
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * @author ElonTusk
+ * @name BRPC
+ * @date 2023/8/17 16:27
+ */
+public class BRPCLimit extends AbstractLimit {
+
+    private static class BRPCLimitContainer {
+        private static BRPCLimit instance = new BRPCLimit();
+    }
+
+    public static AbstractLimit getInstance() {
+        return BRPCLimit.BRPCLimitContainer.instance;
+    }
+
+    double alpha = 0.3;
+    double min_explore_ratio = 1.1;
+    double max_explore_ratio = 1.3;
+    double correction_factor = 0.1;
+    double change_step = 0.02;
+    double maxQps = 0;
+    double explore_ratio = 1;
+
+    @Override
+    public int update(AdaptiveRule rule, StatisticNode node) {
+        double minRt = node.minRt();
+        double rt = node.avgRt();
+        double passQps = node.passQps();
+        double emaFactor = alpha / 10;
+        if (passQps >= maxQps) {
+            maxQps = passQps;
+        } else {
+            maxQps = passQps * emaFactor + maxQps * (1 - emaFactor);
+        }
+
+        //double maxConcurrency = maxQps * ((1 + alpha) * minRt  - rt) / 1000;
+        if (rt <= minRt * (1.0 + min_explore_ratio * correction_factor) ||
+                passQps <= maxQps / (1.0 + min_explore_ratio)) {
+            explore_ratio = Math.min(max_explore_ratio, explore_ratio + change_step);
+        } else {
+            explore_ratio = Math.max(min_explore_ratio, explore_ratio - change_step);
+        }
+        double maxConcurrency =
+                minRt * maxQps * (1 + explore_ratio);
+        System.out.println("\n" + maxQps + " " + explore_ratio);
+        int maxQps = (int) (maxConcurrency / rt);
+        maxQps = Math.max(10, Math.min(maxQps, 1000));
+        return maxQps;
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/GradientLimit.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/GradientLimit.java
new file mode 100644
index 00000000..3674e5f2
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/GradientLimit.java
@@ -0,0 +1,79 @@
+package com.alibaba.csp.sentinel.slots.adaptive.algorithm;
+
+import com.alibaba.csp.sentinel.node.StatisticNode;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRule;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author ElonTusk
+ * @name GradientLimit
+ * @date 2023/8/17 15:28
+ */
+public class GradientLimit extends AbstractLimit {
+    private static class GradientLimitContainer {
+        private static GradientLimit instance = new GradientLimit();
+    }
+
+    public static AbstractLimit getInstance() {
+        return GradientLimit.GradientLimitContainer.instance;
+    }
+
+    private int minLimit = 10;
+    private int maxLimit = 200;
+    private int window = 60;
+    private int warmupWindow = 10;
+    private double tolerance = 0.4;
+
+    @Override
+    public int update(AdaptiveRule rule, StatisticNode node) {
+        Queue<Integer> oldLimits = rule.getOldCounts();
+        double minRt = node.minRt();
+        double rt = node.avgRt();
+        double estimatedQps = 20;
+        for (Integer oldLimit : oldLimits) {
+            estimatedQps = oldLimit;
+        }
+        double estimatedLimit = estimatedQps * rt;
+        final double queueSize = Math.sqrt(estimatedLimit);
+
+        double shortRtt = minRt;
+        double longRtt = calLongRtt(rt);
+
+
+        if (longRtt / shortRtt > 2) {
+            longRtt = longRtt * 0.95;
+        }
+
+        final double gradient = Math.max(0.5, Math.min(1.0, tolerance * longRtt / shortRtt));
+        double newLimit = estimatedLimit * gradient + queueSize;
+        newLimit = estimatedLimit * (1 - RuleConstant.ADAPTIVE_LIMIT_SMOOTHING) + newLimit * RuleConstant.ADAPTIVE_LIMIT_SMOOTHING;
+        newLimit = Math.max(minLimit * rt, Math.min(maxLimit * rt, newLimit));
+
+        estimatedQps = newLimit / rt;
+        return (int) estimatedQps;
+    }
+
+    private AtomicInteger count = new AtomicInteger(0);
+
+    private double sum = 0.0;
+    private double value = 0.0;
+
+    private double calLongRtt(double rt) {
+        if (count.get() < warmupWindow) {
+            count.incrementAndGet();
+            sum += rt;
+            value = sum / count.get();
+        } else {
+            double factor = factor(window);
+            value = value * (1 - factor) + rt * factor;
+        }
+        return value;
+    }
+
+    private double factor(int n) {
+        return 2.0 / (n + 1);
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/VegasLimit.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/VegasLimit.java
new file mode 100644
index 00000000..cb46a23a
--- /dev/null
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/adaptive/algorithm/VegasLimit.java
@@ -0,0 +1,64 @@
+package com.alibaba.csp.sentinel.slots.adaptive.algorithm;
+
+import java.util.Queue;
+
+import com.alibaba.csp.sentinel.node.StatisticNode;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRule;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+
+/**
+ * @author ElonTusk
+ * @name Vegas
+ * @date 2023/8/2 14:47
+ */
+public class VegasLimit extends AbstractLimit {
+    private VegasLimit() {
+    }
+
+    private static class VegasLimitContainer {
+        private static VegasLimit instance = new VegasLimit();
+    }
+
+    public static AbstractLimit getInstance() {
+        return VegasLimitContainer.instance;
+    }
+
+    @Override
+    public int update(AdaptiveRule rule, StatisticNode node) {
+        Queue<Integer> oldLimits = rule.getOldCounts();
+        double minRt = node.minRt();
+        double rt = node.avgRt();
+        // 适合微调，不太适合系统状态有大幅度变化的场景，因为每次调整的大小只有 log10(Limit) ，而且还有smoothing系数
+        double estimatedQps = 0;
+        for (Integer oldLimit : oldLimits) {
+            estimatedQps = oldLimit;
+        }
+        double estimatedLimit = estimatedQps * rt;
+        final int queueSize = (int) Math.ceil(estimatedLimit * (1 - minRt / rt));
+
+        double newLimit;
+        // Treat any drop (i.e timeout) as needing to reduce the limit
+
+        double alpha = 3 * Math.log10(estimatedLimit);
+        double beta = 6 * Math.log10(estimatedLimit);
+        double threshold = Math.log10(estimatedLimit);
+
+        // Aggressive increase when no queuing
+        if (queueSize <= threshold) {
+            newLimit = estimatedLimit + beta;
+            // Increase the limit if queue is still manageable
+        } else if (queueSize < alpha) {
+            newLimit = estimatedLimit + Math.log10(estimatedLimit);
+            // Detecting latency so decrease
+        } else if (queueSize > beta) {
+            newLimit = estimatedLimit - Math.log10(estimatedLimit);
+            // We're within he sweet spot so nothing to do
+        } else {
+            return (int) estimatedLimit;
+        }
+        //newLimit = Math.max(1, Math.min(maxLimit, newLimit));
+        newLimit = (1 - RuleConstant.ADAPTIVE_LIMIT_SMOOTHING) * estimatedLimit + RuleConstant.ADAPTIVE_LIMIT_SMOOTHING * newLimit;
+        int newQps = (int) (newLimit / rt);
+        return newQps;
+    }
+}
diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
index 0b157675..109d7fd5 100755
--- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
+++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java
@@ -65,5 +65,18 @@ public final class RuleConstant {
     public static final int DEFAULT_SAMPLE_COUNT = 2;
     public static final int DEFAULT_WINDOW_INTERVAL_MS = 1000;
 
-    private RuleConstant() {}
+    public static final int ADAPTIVE_VEGAS = 0;
+
+    public static final int ADAPTIVE_GRADIENT = 1;
+
+    public static final int ADAPTIVE_BRPC = 2;
+
+    public static final double ADAPTIVE_LIMIT_SMOOTHING = 0.6;
+
+    public static final int ADAPTIVE_LIMIT_THRESHOLD = 1000;
+
+    public static final int OLD_COUNTS_MAX_SIZE = 10;
+
+    private RuleConstant() {
+    }
 }
diff --git a/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot b/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
index 867777ce..bccc73f7 100644
--- a/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
+++ b/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
@@ -5,6 +5,7 @@ com.alibaba.csp.sentinel.slots.logger.LogSlot
 com.alibaba.csp.sentinel.slots.statistic.StatisticSlot
 com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot
 com.alibaba.csp.sentinel.slots.system.SystemSlot
+com.alibaba.csp.sentinel.slots.adaptive.AdaptiveSlot
 com.alibaba.csp.sentinel.slots.block.flow.FlowSlot
 com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
 com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot
\ No newline at end of file
diff --git a/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/adaptive/AdaptiveDemo.java b/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/adaptive/AdaptiveDemo.java
new file mode 100644
index 00000000..ef1244da
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/adaptive/AdaptiveDemo.java
@@ -0,0 +1,187 @@
+package com.alibaba.csp.sentinel.demo.adaptive;
+
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.demo.flow.FlowQpsDemo;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRule;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveRuleManager;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.BRPCLimit;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.GradientLimit;
+import com.alibaba.csp.sentinel.slots.adaptive.algorithm.VegasLimit;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.csp.sentinel.util.TimeUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author ElonTusk
+ * @name AdaptiveDemo
+ * @date 2023/8/7 16:08
+ */
+public class AdaptiveDemo {
+    private static final String KEY = "abc";
+
+    public static void main(String[] args) {
+        initAdaptiveRule();
+
+        tick();
+        // first make the system run on a very low condition
+        simulateTraffic();
+
+        System.out.println("===== begin to do flow control");
+
+    }
+
+    private static void initAdaptiveRule() {
+        List<AdaptiveRule> rules = new ArrayList<>();
+        AdaptiveRule rule1 = new AdaptiveRule();
+        rule1.setResource(KEY);
+        // set init limit qp
+        // init count 20
+        int initCount = 10;
+        rule1.setCount(initCount);
+        rule1.addCount(initCount);
+//        rule1.setStrategy(RuleConstant.ADAPTIVE_VEGAS);
+//        rule1.setLimiter(VegasLimit.getInstance());
+//        rule1.setStrategy(RuleConstant.ADAPTIVE_GRADIENT);
+//        rule1.setLimiter(GradientLimit.getInstance());
+        rule1.setStrategy(RuleConstant.ADAPTIVE_BRPC);
+        rule1.setLimiter(BRPCLimit.getInstance());
+        rules.add(rule1);
+        AdaptiveRuleManager.loadRules(rules);
+
+        List<FlowRule> rules2 = new ArrayList<>();
+        FlowRule rule2 = new FlowRule();
+        rule2.setResource(KEY);
+        rule2.setCount(initCount);
+        rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
+        rule2.setLimitApp("default");
+        rules2.add(rule2);
+        FlowRuleManager.loadRules(rules2);
+    }
+
+    private static void tick() {
+        Thread timer = new Thread(new TimerTask());
+        timer.setName("sentinel-timer-task");
+        timer.start();
+    }
+
+    private static volatile boolean stop = false;
+    private static AtomicInteger pass = new AtomicInteger();
+    private static AtomicInteger block = new AtomicInteger();
+    private static AtomicInteger total = new AtomicInteger();
+
+    private static final int threadCount = 10;
+
+    private static int seconds = 60 + 40;
+
+    static class TimerTask implements Runnable {
+
+        @Override
+        public void run() {
+            long start = System.currentTimeMillis();
+            System.out.println("begin to statistic!!!");
+
+            long oldTotal = 0;
+            long oldPass = 0;
+            long oldBlock = 0;
+            while (!stop) {
+                try {
+                    TimeUnit.SECONDS.sleep(1);
+                } catch (InterruptedException e) {
+                }
+                long globalTotal = total.get();
+                long oneSecondTotal = globalTotal - oldTotal;
+                oldTotal = globalTotal;
+
+                long globalPass = pass.get();
+                long oneSecondPass = globalPass - oldPass;
+                oldPass = globalPass;
+
+                long globalBlock = block.get();
+                long oneSecondBlock = globalBlock - oldBlock;
+                oldBlock = globalBlock;
+
+                System.out.println(seconds + " send qps is: " + oneSecondTotal);
+                System.out.println(TimeUtil.currentTimeMillis() + ", total:" + oneSecondTotal
+                        + ", pass:" + oneSecondPass
+                        + ", block:" + oneSecondBlock);
+
+                if (seconds-- <= 0) {
+                    stop = true;
+                }
+            }
+
+            long cost = System.currentTimeMillis() - start;
+            System.out.println("time cost: " + cost + " ms");
+            System.out.println("total:" + total.get() + ", pass:" + pass.get()
+                    + ", block:" + block.get());
+            System.exit(0);
+        }
+    }
+
+    private static void simulateTraffic() {
+        for (int i = 0; i < threadCount; i++) {
+            Thread t = new Thread(new RunTask());
+            t.setName("simulate-traffic-Task");
+            t.start();
+        }
+    }
+
+    static Random random = new Random();
+
+    static class RunTask implements Runnable {
+        @Override
+        public void run() {
+            while (!stop) {
+                Entry entry = null;
+
+                try {
+                    entry = SphU.entry(KEY, EntryType.IN);
+                    // token acquired, means pass
+                    try {
+                        TimeUnit.MILLISECONDS.sleep(90 + random.nextInt(20));
+                        //TimeUnit.MILLISECONDS.sleep(50);
+                        //CpuRunMethod();
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                    pass.addAndGet(1);
+
+                } catch (BlockException e1) {
+                    block.incrementAndGet();
+                    try {
+                        //TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
+                        TimeUnit.MILLISECONDS.sleep(0);
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                } catch (Exception e2) {
+                    // biz exception
+                } finally {
+                    total.incrementAndGet();
+                    if (entry != null) {
+                        entry.exit();
+                    }
+                }
+
+                Random random2 = new Random();
+                try {
+                    //TimeUnit.MILLISECONDS.sleep(random2.nextInt(100));
+                    TimeUnit.MILLISECONDS.sleep(0);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+}


Test patch:
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
index 9527dc39..364d4745 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java
@@ -17,6 +17,7 @@ package com.alibaba.csp.sentinel.slots;
 
 import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
 import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveSlot;
 import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot;
 import com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot;
 import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot;
@@ -65,6 +66,9 @@ public class DefaultSlotChainBuilderTest {
         next = next.getNext();
         assertTrue(next instanceof SystemSlot);
 
+        next = next.getNext();
+        assertTrue(next instanceof AdaptiveSlot);
+
         next = next.getNext();
         assertTrue(next instanceof FlowSlot);
 
diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/SpiLoaderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/SpiLoaderTest.java
index 2aa1c921..0285cb59 100644
--- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/SpiLoaderTest.java
+++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/spi/SpiLoaderTest.java
@@ -21,6 +21,7 @@ import com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit;
 import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
 import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder;
 import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
+import com.alibaba.csp.sentinel.slots.adaptive.AdaptiveSlot;
 import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot;
 import com.alibaba.csp.sentinel.slots.block.degrade.DefaultCircuitBreakerSlot;
 import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot;
@@ -100,11 +101,12 @@ public class SpiLoaderTest {
         prototypeSlotClasses.add(NodeSelectorSlot.class);
         prototypeSlotClasses.add(ClusterBuilderSlot.class);
 
-        List<Class<? extends ProcessorSlot>> singletonSlotClasses = new ArrayList<>(7);
+        List<Class<? extends ProcessorSlot>> singletonSlotClasses = new ArrayList<>(8);
         singletonSlotClasses.add(LogSlot.class);
         singletonSlotClasses.add(StatisticSlot.class);
         singletonSlotClasses.add(AuthoritySlot.class);
         singletonSlotClasses.add(SystemSlot.class);
+        singletonSlotClasses.add(AdaptiveSlot.class);
         singletonSlotClasses.add(FlowSlot.class);
         singletonSlotClasses.add(DegradeSlot.class);
         singletonSlotClasses.add(DefaultCircuitBreakerSlot.class);
@@ -151,7 +153,7 @@ public class SpiLoaderTest {
         assertNotNull(sortedSlots);
 
         // Total 8 default slot in sentinel-core
-        assertEquals(9, sortedSlots.size());
+        assertEquals(10, sortedSlots.size());
 
         // Verify the order of slot
         int index = 0;
@@ -161,6 +163,7 @@ public class SpiLoaderTest {
         assertTrue(sortedSlots.get(index++) instanceof StatisticSlot);
         assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot);
         assertTrue(sortedSlots.get(index++) instanceof SystemSlot);
+        assertTrue(sortedSlots.get(index++) instanceof AdaptiveSlot);
         assertTrue(sortedSlots.get(index++) instanceof FlowSlot);
         assertTrue(sortedSlots.get(index++) instanceof DefaultCircuitBreakerSlot);
         assertTrue(sortedSlots.get(index++) instanceof DegradeSlot);