跳到主要内容

流量控制

流量控制(Flow Control)是 Sentinel 最核心的功能之一。其原理是监控应用流量的 QPS(每秒查询数)或并发线程数等指标,当达到指定的阈值时对流量进行控制,避免系统被瞬时流量高峰冲垮。

流量控制原理

Sentinel 的流量控制通过 FlowSlot 实现。当请求进入资源时,FlowSlot 会根据预设的规则,结合 StatisticSlot 统计出来的实时信息进行流量控制判断。

限流的直接表现是在执行 SphU.entry(resourceName) 时抛出 FlowException 异常。FlowExceptionBlockException 的子类,可以通过捕获 BlockException 来自定义被限流后的处理逻辑。

流量控制规则

一条流量控制规则(FlowRule)包含以下核心属性:

属性说明默认值
resource资源名,规则的作用对象必填
count限流阈值必填
grade限流阈值类型(QPS 或并发线程数)QPS
limitApp流控针对的调用来源default(不区分来源)
strategy调用关系限流策略直接
controlBehavior流量控制效果直接拒绝

基于 QPS 的流量控制

QPS(Queries Per Second)限流是最常用的限流方式,限制资源每秒允许通过的请求数。

基本配置

List<FlowRule> rules = new ArrayList<>();

FlowRule rule = new FlowRule();
rule.setResource("getUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS 模式
rule.setCount(100); // 每秒最多 100 个请求

rules.add(rule);
FlowRuleManager.loadRules(rules);

流控效果

当 QPS 超过阈值时,Sentinel 提供三种处理方式:

1. 直接拒绝(默认)

当 QPS 超过阈值后,新的请求立即被拒绝,抛出 FlowException。这种方式适用于对系统处理能力明确已知的场景。

FlowRule rule = new FlowRule();
rule.setResource("getUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 直接拒绝

直接拒绝的效果:

请求 QPS: 150
阈值: 100
结果: 100 个请求通过,50 个请求被拒绝

2. Warm Up(预热)

当系统长期处于低水位状态,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。Warm Up 模式让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热时间。

FlowRule rule = new FlowRule();
rule.setResource("getUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); // Warm Up
rule.setWarmUpPeriodSec(10); // 预热时间 10 秒

Warm Up 模式基于令牌桶算法实现,冷启动过程中系统允许通过的 QPS 曲线如下:

QPS
^
| ________
| /
| /
| /
|_______/
+------------------> 时间
0 10s

预热过程说明:

  • 初始时刻,系统处于冷状态,QPS 阈值较低(默认为 count / coldFactor,coldFactor 默认为 3)
  • 随着时间推移,阈值逐渐增加
  • 经过 warmUpPeriodSec 秒后,阈值达到设定的 count 值

适用场景:

  • 秒杀系统开启前
  • 缓存预热场景
  • 服务刚启动时

3. 匀速排队

匀速排队模式严格控制请求通过的间隔时间,让请求以均匀的速度通过,对应的是漏桶算法。

FlowRule rule = new FlowRule();
rule.setResource("getUser");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 匀速排队
rule.setMaxQueueingTimeMs(500); // 最大排队等待时间 500ms

匀速排队的效果:

请求到达时间: 0ms, 10ms, 20ms, 30ms, ... (突发)
处理间隔: 10ms (QPS=100)
结果: 请求被均匀处理,间隔 10ms

适用场景:

  • 消息队列削峰填谷
  • 间隔性突发流量场景

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

基于并发线程数的流量控制

并发线程数限流用于保护业务线程池不被慢调用耗尽。当应用依赖的下游服务响应延迟增加时,会导致调用方吞吐量下降和更多线程占用,极端情况下导致线程池耗尽。

工作原理

Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝。

FlowRule rule = new FlowRule();
rule.setResource("callRemoteService");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); // 线程数模式
rule.setCount(10); // 最多 10 个并发线程

与 QPS 限流的区别

特性QPS 限流线程数限流
统计维度每秒请求数当前并发数
适用场景保护系统不被高 QPS 压垮保护线程池不被慢调用耗尽
响应时间影响不受响应时间影响受响应时间影响
实现方式令牌桶/漏桶信号量

适用场景

// 场景:调用远程服务,服务可能变慢
@SentinelResource(value = "remoteCall", blockHandler = "handleBlock")
public String callRemoteService() {
// 如果远程服务响应慢,线程会被占用
// 线程数限流可以防止线程池耗尽
return remoteService.call();
}

基于调用关系的流量控制

Sentinel 支持根据调用来源进行流量控制,实现更精细化的限流策略。

调用方识别

调用方通过 ContextUtil.enter(resourceName, origin) 中的 origin 参数标识:

// 调用方设置来源
ContextUtil.enter("getUser", "appA");
try {
Entry entry = SphU.entry("getUser");
// 业务逻辑
entry.exit();
} finally {
ContextUtil.exit();
}

根据调用方限流

通过 limitApp 字段配置:

// 规则一:针对 appA 的限流
FlowRule rule1 = new FlowRule();
rule1.setResource("getUser");
rule1.setLimitApp("appA"); // 只针对 appA
rule1.setCount(10);

// 规则二:针对其他调用方的限流
FlowRule rule2 = new FlowRule();
rule2.setResource("getUser");
rule2.setLimitApp("other"); // 针对除 appA 外的其他调用方
rule2.setCount(5);

// 规则三:默认规则
FlowRule rule3 = new FlowRule();
rule3.setResource("getUser");
rule3.setLimitApp("default"); // 不区分调用方
rule3.setCount(20);

规则生效优先级:{some_origin_name} > other > default

关联流量控制

当两个资源之间存在资源争抢或依赖关系时,可以使用关联限流。例如,数据库的读操作和写操作存在争抢,可以设置写操作优先。

// 当 writeDB 的 QPS 超过阈值时,限制 readDB
FlowRule rule = new FlowRule();
rule.setResource("readDB");
rule.setStrategy(RuleConstant.STRATEGY_RELATE); // 关联模式
rule.setRefResource("writeDB"); // 关联的资源
rule.setCount(100);

链路流量控制

链路限流只记录从入口资源开始的流量。

// 只记录从 entrance1 进入的流量
FlowRule rule = new FlowRule();
rule.setResource("nodeA");
rule.setStrategy(RuleConstant.STRATEGY_CHAIN); // 链路模式
rule.setRefResource("entrance1"); // 入口资源
rule.setCount(100);

调用链示例:

machine-root
|
+-- entrance1
| |
| +-- nodeA
|
+-- entrance2
|
+-- nodeA

配置链路限流后,只有从 entrance1 进入的请求会被统计和限流,从 entrance2 进入的请求不受影响。

实时监控数据

可以通过 HTTP API 查看实时统计数据:

# 查看资源的实时统计
curl http://localhost:8719/cnode?id=getUser

输出示例:

idx id      thread pass blocked success total rt  1m-pass 1m-block 1m-all exception
1 getUser 5 100 50 100 150 10 6000 3000 9000 0

字段说明:

  • thread:当前处理该资源的并发线程数
  • pass:一秒内到达的请求数
  • blocked:一秒内被限流的请求数
  • success:一秒内成功处理的请求数
  • total:一秒内到达和被阻止的请求总和
  • rt:平均响应时间(毫秒)
  • 1m-pass:一分钟内到达的请求数
  • 1m-block:一分钟内被阻止的请求数
  • exception:一秒内业务异常数

代码示例

完整的流量控制示例

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.context.ContextUtil;
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 java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FlowControlDemo {

public static void main(String[] args) throws InterruptedException {
initFlowRules();

ExecutorService executor = Executors.newFixedThreadPool(20);

for (int i = 0; i < 20; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 100; j++) {
// 设置调用来源
ContextUtil.enter("getUser", "app" + (threadId % 3));

Entry entry = null;
try {
entry = SphU.entry("getUser");
System.out.println("Thread-" + threadId + ": request passed");
Thread.sleep(50);
} catch (BlockException e) {
System.out.println("Thread-" + threadId + ": request blocked");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
});
}

Thread.sleep(10000);
executor.shutdown();
}

private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();

// QPS 限流,每秒最多 10 个请求,使用 Warm Up
FlowRule rule1 = new FlowRule();
rule1.setResource("getUser");
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setCount(10);
rule1.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule1.setWarmUpPeriodSec(5);

rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
}

最佳实践

1. 选择合适的限流维度

  • QPS 限流:适用于保护系统不被高并发压垮
  • 线程数限流:适用于保护线程池不被慢调用耗尽

2. 合理设置阈值

  • 通过压测确定系统的最佳处理能力
  • 设置阈值时留有一定余量(如 70%-80% 的处理能力)
  • 考虑业务高峰期的流量特征

3. 选择合适的流控效果

  • 直接拒绝:系统处理能力明确,需要快速失败
  • Warm Up:系统需要预热,避免冷启动问题
  • 匀速排队:需要削峰填谷,平滑处理请求

4. 监控和调优

  • 定期查看监控数据,了解流量分布
  • 根据实际情况调整阈值和策略
  • 设置告警,及时发现异常

下一步