流量控制
流量控制(Flow Control)是 Sentinel 最核心的功能之一。其原理是监控应用流量的 QPS(每秒查询数)或并发线程数等指标,当达到指定的阈值时对流量进行控制,避免系统被瞬时流量高峰冲垮。
流量控制原理
Sentinel 的流量控制通过 FlowSlot 实现。当请求进入资源时,FlowSlot 会根据预设的规则,结合 StatisticSlot 统计出来的实时信息进行流量控制判断。
限流的直接表现是在执行 SphU.entry(resourceName) 时抛出 FlowException 异常。FlowException 是 BlockException 的子类,可以通过捕获 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. 监控和调优
- 定期查看监控数据,了解流量分布
- 根据实际情况调整阈值和策略
- 设置告警,及时发现异常