![图片[1]-限流降级与流量控制(上篇):FlowSlot核心原理 - 速优课-速优课](http://www.suyouke.com/wp-content/uploads/2026/08/doubao_img_2304x1728_20260803_094414-1024x768.png)
限流降级与流量控制(上篇):FlowSlot核心原理
前言
前面我们花了三篇文章,把Sentinel的指标统计体系完整地讲了一遍。从这篇开始,我们正式进入功能模块的分析。
第一个要讲的,就是最核心、最常用的限流降级——也就是FlowSlot。
限流是Sentinel最基础也是最重要的功能之一。这篇我们先从整体架构入手,看看限流规则是怎么管理的、FlowSlot是怎么工作的、以及限流检查的完整流程。
一、限流功能的整体架构
在看具体代码之前,我们先搞清楚Sentinel限流功能的整体设计。
Sentinel的各种降级功能(限流、熔断、系统保护等),基本上都是由这几个角色配合完成的:
| 角色 | 作用 | 限流对应的类 |
|---|---|---|
| ProcessorSlot | 责任链切入点,调用Checker做检查 | FlowSlot |
| Checker | 规则检查器,负责具体的判断逻辑 | FlowRuleChecker |
| Rule | 规则配置类,保存阈值、策略等配置 | FlowRule |
| RuleManager | 规则管理器,缓存和加载规则 | FlowRuleManager |
整体流程可以总结为三步:
- FlowSlot.entry() 被调用,拿到当前资源的DefaultNode
- FlowRuleChecker 从FlowRuleManager取出该资源的所有限流规则
- 逐条规则检查,用ClusterNode的实时指标和规则阈值对比,达到阈值就抛出FlowException
就这么简单。下面我们一个个角色来看。
二、限流规则:FlowRule
2.1 Rule接口
先从最顶层的Rule接口说起。
Sentinel最初设计的时候,把”判断请求是否能通过”这个行为交给了Rule自己,所以定义了Rule接口:
public interface Rule {
boolean passCheck(Context context, DefaultNode node, int count, Object... args);
}
参数说明:
context:调用链路上下文node:当前资源的DefaultNodecount:申请数量(一般是1,令牌桶算法里就是申请几个令牌)args:方法调用参数(热点参数限流会用到)
2.2 AbstractRule抽象类
因为规则都是围绕资源配置的,所以有个抽象类AbstractRule:
public abstract class AbstractRule implements Rule {
private String resource; // 资源名称
private String limitApp; // 对哪个调用来源生效
}
resource:规则作用在哪个资源上limitApp:只对某个调用来源生效,default表示不区分来源
各种规则的继承关系:
![图片[2]-限流降级与流量控制(上篇):FlowSlot核心原理 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/image-21-1024x388.png)
2.3 FlowRule限流规则
FlowRule是限流规则的配置类,字段比较多,我们一个个来看:
public class FlowRule extends AbstractRule {
// 限流阈值类型:QPS或线程数
private int grade = RuleConstant.FLOW_GRADE_QPS;
// 限流阈值
private double count;
// 基于调用关系的限流策略
private int strategy = RuleConstant.STRATEGY_DIRECT;
// 引用资源名称(配合strategy使用)
private String refResource;
// 流量控制效果:直接拒绝、Warm Up、匀速排队
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
// 冷启动时长(秒)
private int warmUpPeriodSec = 10;
// 最大排队时间(毫秒)
private int maxQueueingTimeMs = 500;
// 流量控制器
private TrafficShapingController controller;
@Override
public boolean passCheck(Context context, DefaultNode node,
int acquireCount, Object... args) {
return true;
}
}
注意一个细节:FlowRule的passCheck方法直接返回true。
也就是说,Sentinel自己都没遵守最初的设计约定——passCheck的逻辑并不在Rule里实现,而是放到了Checker里。这应该是框架演进过程中的历史遗留问题。
2.4 FlowRuleManager规则管理器
规则的加载和缓存由FlowRuleManager负责:
public class FlowRuleManager {
// 缓存规则:key=资源名称,value=该资源的所有限流规则
private static final Map<String, List<FlowRule>> flowRules =
new ConcurrentHashMap<String, List<FlowRule>>();
static Map<String, List<FlowRule>> getFlowRuleMap() {
return flowRules;
}
// 加载/更新规则
public static void loadRules(List<FlowRule> rules) {
// 更新静态字段flowRules
}
}
几个关键点:
- 用ConcurrentHashMap缓存:key是资源名,value是规则列表
- 一个资源可以有多条规则:只要有一条规则触发限流,就拒绝请求
- loadRules方法更新规则:先清空再写入,动态生效
三、FlowSlot:限流切入点
FlowSlot是限流功能在责任链中的切入点。代码很简单:
public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
private final FlowRuleChecker checker;
public FlowSlot() {
this(new FlowRuleChecker());
}
// 规则提供者:根据资源名获取规则列表
private final Function<String, Collection<FlowRule>> ruleProvider =
new Function<String, Collection<FlowRule>>() {
@Override
public Collection<FlowRule> apply(String resource) {
Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
return flowRules.get(resource);
}
};
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node,
int count, boolean prioritized, Object... args) throws Throwable {
// 先做限流检查
checkFlow(resourceWrapper, context, node, count, prioritized);
// 检查通过,传给下一个Slot
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);
}
}
FlowSlot做的事情很简单:
- 构造方法里创建FlowRuleChecker
- entry方法里调用checker.checkFlow()做限流检查
- 检查通过就继续往下传,不通过就抛异常
注意这里的ruleProvider——它是一个Function接口,封装了”根据资源名获取规则列表”这个行为。这样FlowRuleChecker就不需要直接依赖FlowRuleManager了,耦合度更低。
四、FlowRuleChecker:限流检查器
FlowRuleChecker是真正做限流判断的地方。我们顺着调用链一步步往下看。
4.1 checkFlow:入口方法
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider,
ResourceWrapper resource, Context context, DefaultNode node,
int count, boolean prioritized) throws BlockException {
if (ruleProvider == null || resource == null) {
return;
}
// 1. 获取当前资源的所有限流规则
Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
if (rules != null) {
// 2. 遍历每条规则
for (FlowRule rule : rules) {
// 3. 检查是否能通过
if (!canPassCheck(rule, context, node, count, prioritized)) {
// 4. 通不过就抛FlowException
throw new FlowException(rule.getLimitApp(), rule);
}
}
}
}
逻辑很清晰:
- 拿到该资源的所有限流规则
- 逐条检查
- 只要有一条规则不通过,就抛FlowException(BlockException的子类)
为什么一个资源可以有多条规则?因为你可以同时配置多种限流策略,比如:QPS不超过100,同时并发线程数不超过20。两条规则同时生效,哪个先到就触发哪个。
4.2 canPassCheck:判断是否通过
public boolean canPassCheck(FlowRule rule, Context context, DefaultNode node,
int acquireCount, boolean prioritized) {
// 1. limitApp为空直接通过
String limitApp = rule.getLimitApp();
if (limitApp == null) {
return true;
}
// 2. 集群限流模式
if (rule.isClusterMode()) {
return passClusterCheck(rule, context, node, acquireCount, prioritized);
}
// 3. 单机限流模式
return passLocalCheck(rule, context, node, acquireCount, prioritized);
}
三种情况:
- limitApp为空 → 直接通过(一般不会出现,默认是default)
- 集群模式 → 走集群限流检查(后面再讲)
- 单机模式 → 走单机限流检查
4.3 passLocalCheck:单机限流检查
private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node,
int acquireCount, boolean prioritized) {
// 1. 根据策略选择要检查的Node
Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node);
if (selectedNode == null) {
return true;
}
// 2. 拿到流量控制器,调用canPass
return rule.getRater()
.canPass(selectedNode, acquireCount, prioritized);
}
两步走:
- 选Node:根据limitApp和strategy,选择用哪个Node的统计数据来判断
- 调用控制器:拿到TrafficShapingController(流量效果控制器),调用它的canPass方法
TrafficShapingController就是我们常说的”流量效果控制器”——直接拒绝、冷启动、匀速排队,都是由它来实现的。这个我们后面几篇会详细讲。
重点来看第一步:怎么选Node?
五、选择统计节点:selectNodeByRequesterAndStrategy
这个方法是限流检查里最复杂的一个方法,因为它要处理多种组合情况。
static Node selectNodeByRequesterAndStrategy(FlowRule rule, Context context, DefaultNode node) {
String limitApp = rule.getLimitApp(); // 限制哪个来源
int strategy = rule.getStrategy(); // 限流策略
String origin = context.getOrigin(); // 当前调用来源
// 情况1:当前来源正好是规则指定的来源
if (limitApp.equals(origin) && filterOrigin(origin)) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return context.getOriginNode(); // (1) 直接限流:用来源Node
}
return selectReferenceNode(rule, context, node); // (2) 关联/链路限流
}
// 情况2:规则是default(不区分来源)
else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return node.getClusterNode(); // (3) 直接限流:用ClusterNode
}
return selectReferenceNode(rule, context, node); // (4) 关联/链路限流
}
// 情况3:规则是other(其他来源)
else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp)
&& FlowRuleManager.isOtherOrigin(origin, rule.getResource())) {
if (strategy == RuleConstant.STRATEGY_DIRECT) {
return context.getOriginNode(); // (5) 直接限流:用来源Node
}
return selectReferenceNode(rule, context, node); // (6) 关联/链路限流
}
return null;
}
看起来有点晕,我们来梳理一下。
5.1 三个limitApp选项
limitApp有三种可能的值:
| limitApp值 | 含义 |
|---|---|
default | 不区分来源,对所有调用者生效 |
| 具体的来源名(如service-a) | 只对这个来源生效 |
other | 对其他来源生效(除了已经单独配置的) |
5.2 三个限流策略
strategy也有三种:
| 策略 | 含义 | 说明 |
|---|---|---|
STRATEGY_DIRECT | 直接限流 | 用当前资源的指标数据判断 |
STRATEGY_RELATE | 关联限流 | 用另一个资源(refResource)的指标数据判断 |
STRATEGY_CHAIN | 链路限流 | 用调用链路上的DefaultNode判断 |
3 × 3 = 9种组合?其实不是,因为有些组合最终走的是同一个逻辑。简化一下,核心是这6种情况:
| 序号 | limitApp | strategy | 使用的Node | 说明 |
|---|---|---|---|---|
| 1 | 指定来源 | DIRECT | origin的StatisticNode | 只限制某个来源的QPS |
| 2 | 指定来源 | RELATE/CHAIN | 引用资源/链路Node | 对某个来源,按关联资源限流 |
| 3 | default | DIRECT | ClusterNode | 最常用:全局限流 |
| 4 | default | RELATE/CHAIN | 引用资源/链路Node | 全局按关联资源限流 |
| 5 | other | DIRECT | origin的StatisticNode | 对”其他来源”限流 |
| 6 | other | RELATE/CHAIN | 引用资源/链路Node | 对”其他来源”按关联资源限流 |
5.3 为什么需要这么多策略?
你可能会问:搞这么复杂,有必要吗?
我们举几个实际场景,你就明白了。
场景1:按调用来源限流
你的服务同时被服务A和服务B调用。服务A是核心业务,服务B是非核心业务。你想限制服务B的QPS,但不限制服务A。
这时候就可以配置两条规则:
- limitApp = service-b,count = 50(限制服务B最多50 QPS)
- limitApp = default,count = 500(总共最多500 QPS)
场景2:关联限流(写优先)
你的服务有两个接口:
- 读接口:queryOrder
- 写接口:createOrder
两个接口都操作同一张表,写操作优先级更高。你希望写操作太多的时候,限制读操作。
这时候给读接口配置:
- strategy = STRATEGY_RELATE
- refResource = createOrder
- count = 100
意思是:当写接口的QPS超过100时,读接口就限流。实现写优先。
![图片[3]-限流降级与流量控制(上篇):FlowSlot核心原理 - 速优课-速优课](https://www.suyouke.com/wp-content/uploads/2026/08/image-22-1024x562.png)
场景3:链路限流
同一个接口可能从不同的入口进来。比如:
- 从Web入口进来调用的 → 链路A
- 从Dubbo入口进来调用的 → 链路B
你只想限制从Web入口进来的流量,不想限制Dubbo的。
这时候用链路限流(STRATEGY_CHAIN),它用的是DefaultNode的数据——不同链路的DefaultNode是分开的。
这也解释了为什么Sentinel要为同一个资源创建多个DefaultNode——就是为了支持按链路限流。
六、整体流程总结
用一张流程图来总结一下限流检查的完整流程:
请求进入FlowSlot
│
▼
checkFlow()
│
├─→ 获取该资源的所有限流规则
│
└─→ 遍历每条规则
│
▼
canPassCheck()
│
├─→ 集群模式?→ passClusterCheck()
│
└─→ 单机模式 → passLocalCheck()
│
├─→ selectNodeByRequesterAndStrategy() 选择统计Node
│ ├─→ limitApp判断(指定/default/other)
│ └─→ strategy判断(直接/关联/链路)
│
└─→ TrafficShapingController.canPass()
├─→ 直接拒绝模式
├─→ Warm Up冷启动模式
└─→ 匀速排队模式
总结与思考
这篇文章我们从整体架构入手,讲了Sentinel限流功能的四个核心角色:FlowSlot、FlowRuleChecker、FlowRule、FlowRuleManager,以及它们之间的协作流程。
重点回顾:
- 四个角色分工明确:Slot是入口、Checker做判断、Rule存配置、Manager管规则
- 一个资源可以有多条规则:只要有一条触发就限流
- limitApp支持三种模式:指定来源、default(全部)、other(其他)
- strategy支持三种策略:直接限流、关联限流、链路限流
- 3×3的组合:最终通过selectNodeByRequesterAndStrategy选择不同的统计Node
- 真正的限流判断在TrafficShapingController里:直接拒绝、冷启动、匀速排队都由它实现
思考一下:
- 为什么limitApp要有other这个选项?和直接配置default有什么区别?(other是”除了已经单独配置的来源之外的”,可以实现”单独限制A,其他的统一限制B”的需求)
- 关联限流的实际应用场景多吗?(写优先是一个典型场景,总体来说用得不算多,但关键时刻很有用)
- 链路限流为什么需要DefaultNode而不是ClusterNode?(因为ClusterNode是全局的,不分链路;DefaultNode是按Context分的,不同链路的DefaultNode不同)
这篇我们只讲了限流检查的”外壳”,真正的核心——TrafficShapingController(流量效果控制器)还没展开。下一篇,我们就来深入分析三种流量控制效果的实现原理。










请登录后查看评论内容