Sentinel 核心机制全解析:从架构到源码的深度剖析
基于 Sentinel 1.8.x 版本源码深度剖析
说明:本文参考了得物技术公众号《Sentinel Java客户端限流原理解析》的文章结构,结合 Sentinel 官方源码进行深度学习和研究。本文所有技术描述均经过源码验证,旨在通过源码分析深入理解 Sentinel 的核心机制。
【引言】为什么需要理解 Sentinel 的底层机制?
在微服务架构日益普及的今天,服务间的调用关系变得错综复杂。当一个热门商品上线、一场促销活动开始,或者某个第三方服务突然响应缓慢时,我们的系统能否经受住流量的冲击?
实际场景引入
想象一下这样的场景:你的电商系统在"双11"大促期间,某个核心接口突然收到平时的 10 倍流量请求。如果没有有效的流量保护机制:
- 数据库连接池迅速耗尽
- 服务线程池被阻塞请求占满
- 依赖服务因为大量请求而崩溃
- 最终导致整个服务雪崩,业务全面中断
Sentinel 的价值
Sentinel 是阿里开源的、面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保障微服务的稳定性。
但本文不是一篇 API 使用指南。市面上已经有很多关于 Sentinel 如何使用的教程,而很少有人深入探讨它底层的架构设计思想和工作原理,本着学习与分享的精神来一起探索下底层吧。
本文的阅读收益
通过阅读本文,你将:
- 理解架构设计思想:掌握 Sentinel 的整体架构,理解它如何通过责任链模式、观察者模式等设计模式组织复杂的流量控制逻辑
- 掌握模块划分:深入了解 Sentinel 的模块划分,知道每个模块负责什么功能,以及它们之间如何协作
- 理解工作原理:从源码层面理解请求是如何被拦截、规则是如何被加载、流量是如何被统计、限流判断是如何执行的
- 提升源码阅读能力:学习如何从源码中抽象提炼核心思想,而不是简单地复制粘贴
版本说明
本文基于 Sentinel 1.8.x 版本进行分析。
第一章:Sentinel 整体架构全景图
1.1 功能划分
从功能角度,Sentinel 可以划分为以下几个核心模块:
规则管理模块
规则管理模块负责各种流量控制规则的加载、存储和更新,包括:
- 流控规则(FlowRule):控制 QPS 或线程数阈值
- 熔断规则(DegradeRule):当检测到资源响应时间过长或异常比例过高时,对资源进行熔断
- 系统规则(SystemRule):保护整个系统,如 CPU 使用率、平均 RT 等
- 授权规则(AuthorityRule):黑白名单控制,限制某些来源的访问
- 参数流控规则(ParamFlowRule):针对请求参数进行精细化流控
- 热点参数规则:识别热点参数并进行限流
流量拦截模块
流量拦截模块负责在请求进入业务逻辑之前进行拦截,将请求引入 Sentinel 的处理流程。主要拦截方式包括:
- Servlet Filter 拦截:通过
CommonFilter或SentinelServletFilter拦截 HTTP 请求 - Spring MVC 拦截器:通过
SentinelWebInterceptor拦截 Spring MVC 请求 - AOP 切面拦截:通过
@SentinelResource注解和SentinelResourceAspect切面拦截方法调用
核心处理模块
核心处理模块是 Sentinel 的大脑,采用责任链模式,将不同的处理功能组合成一条处理链。主要组件:
- ProcessorSlotChain:功能插槽链,串联各个处理插槽
- 各个 ProcessorSlot:
NodeSelectorSlot:上下文节点选择ClusterBuilderSlot:集群节点构建LogSlot:日志记录StatisticSlot:统计收集AuthoritySlot:权限控制SystemSlot:系统保护FlowSlot:流量控制DegradeSlot:熔断降级ParamFlowSlot:参数流控(位于扩展模块)
数据统计模块
数据统计模块负责收集和存储各种运行时指标,是流控决策的基础:
- 滑动窗口算法:使用
LeapArray实现高效的滑动窗口统计 - 节点树:维护资源调用的树形结构
DefaultNode:特定上下文的统计节点ClusterNode:全局统计节点OriginNode:来源统计节点
1.2 模块分布
下图展示了 Sentinel 客户端的核心组件关系以及请求从进入到流控判断的完整链路:
HTTP 请求
↓
┌────────────────────────────────┐
│ 流量拦截模块 │
│ (Filter/Interceptor/AOP) │
└────────────────────────────────┘
↓
SphU.entry(resourceName)
↓
┌────────────────────────────────┐
│ CtSph.entryWithPriority() │
│ 获取或创建 ProcessorSlotChain │
└────────────────────────────────┘
↓
┌────────────────────────────────┐
│ ProcessorSlotChain 责任链 │
├────────────────────────────────┤
│ NodeSelectorSlot (-10000) │
│ ↓ │
│ ClusterBuilderSlot (-9000) │
│ ↓ │
│ LogSlot (-8000) │
│ ↓ │
│ StatisticSlot (-7000) │ ← 收集统计
│ ↓ │
│ AuthoritySlot (-6000) │
│ ↓ │
│ SystemSlot (-5000) │
│ ↓ │
│ FlowSlot (-2000) │ ← 流控判断
│ ↓ │
│ DegradeSlot (-1000) │
└────────────────────────────────┘
↓
┌────────────────────────────────┐
│ 通过 / 抛出 BlockException │
└────────────────────────────────┘
↓
业务逻辑 / 降级处理
1.3 设计模式概览
Sentinel 的代码中大量使用了经典的设计模式,以下是最核心的几个:
观察者模式(规则动态更新)
规则管理模块使用观察者模式实现规则的动态更新。当规则发生变化时,会通知所有监听器:
- 被观察者:
SentinelProperty接口,DynamicSentinelProperty实现 - 观察者:
PropertyListener接口,如FlowPropertyListener - 通知机制:当调用
updateValue()时,遍历所有监听器并调用configUpdate()
// DynamicSentinelProperty 中的通知逻辑
public boolean updateValue(T newValue) {
if (isEqual(value, newValue)) {
return false;
}
value = newValue;
// 通知所有观察者
for (PropertyListener<T> listener : listeners) {
listener.configUpdate(newValue);
}
return true;
}
责任链模式(ProcessorSlotChain)
核心处理模块使用责任链模式,将不同的处理功能组合成一条链:
- 抽象处理者:
AbstractLinkedProcessorSlot - 具体处理者:各个
ProcessorSlot实现类 - 链的组装:通过 SPI 机制动态加载和排序
- 请求传递:每个 Slot 处理完后调用
fireEntry()传递给下一个 Slot
// AbstractLinkedProcessorSlot 中的传递逻辑
@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);
}
}
模板方法模式(各种 Slot)
各个 Slot 使用模板方法模式定义处理流程,子类实现具体逻辑:
- 抽象模板类:
AbstractLinkedProcessorSlot - 模板方法:
entry()和exit() - 钩子方法:子类实现的
transformEntry()等方法
第二章:规则加载机制 —— 观察者模式的实践
规则是 Sentinel 进行流量控制的依据。理解规则如何加载、如何动态更新,是掌握 Sentinel 工作原理的第一步。
2.1 规则类型概览
Sentinel 支持多种类型的规则,每种规则针对不同的保护场景:
| 规则类型 | 管理类 | 核心作用 |
|---|---|---|
| 流控规则 | FlowRuleManager |
限制 QPS 或并发线程数 |
| 熔断规则 | DegradeRuleManager |
异常比例或慢调用比例熔断 |
| 系统规则 | SystemRuleManager |
保护系统整体负载(CPU、RT) |
| 授权规则 | AuthorityRuleManager |
黑白名单访问控制 |
| 参数流控规则 | ParamFlowRuleManager |
对请求参数进行精细化限流 |
| 热点参数规则 | ParamFlowRuleManager |
识别并限流热点参数 |
2.2 规则加载流程
以流控规则(FlowRule)的加载为例,规则的加载过程经过精心设计的 7 个步骤:
输入: List<FlowRule>
↓
1. 验证规则有效性
↓
2. 应用过滤器
↓
3. 设置默认值
↓
4. 生成限流控制器
↓
5. 按 groupFunction 分组
↓
6. 去重
↓
7. 可选排序
↓
输出: Map<String, List<FlowRule>>
让我们深入 FlowRuleUtil.buildFlowRuleMap() 方法的源码:
public static <K> Map<K, List<FlowRule>> buildFlowRuleMap(
List<FlowRule> list,
Function<FlowRule, K> groupFunction,
Predicate<FlowRule> filter,
boolean shouldSort) {
Map<K, List<FlowRule>> newRuleMap = new ConcurrentHashMap<>();
if (list == null || list.isEmpty()) {
return newRuleMap;
}
Map<K, Set<FlowRule>> tmpMap = new ConcurrentHashMap<>();
// 遍历所有规则
for (FlowRule rule : list) {
// 第1步:验证规则有效性
if (!isValidRule(rule)) {
RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule: " + rule);
continue;
}
// 第2步:应用过滤器
if (filter != null && !filter.test(rule)) {
continue;
}
// 第3步:设置默认值
if (StringUtil.isBlank(rule.getLimitApp())) {
rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
}
// 第4步:生成流量整形控制器
TrafficShapingController rater = generateRater(rule);
rule.setRater(rater);
// 第5步:按资源分组,第6步:去重(使用 HashSet)
K key = groupFunction.apply(rule);
if (key == null) {
continue;
}
Set<FlowRule> flowRules = tmpMap.computeIfAbsent(key, k -> new HashSet<>());
flowRules.add(rule);
}
// 第7步:可选排序
Comparator<FlowRule> comparator = new FlowRuleComparator();
for (Entry<K, Set<FlowRule>> entries : tmpMap.entrySet()) {
List<FlowRule> rules = new ArrayList<>(entries.getValue());
if (shouldSort) {
Collections.sort(rules, comparator);
}
newRuleMap.put(entries.getKey(), rules);
}
return newRuleMap;
}
7 步详解:
- 验证规则有效性:检查资源名、阈值、阈值类型等字段是否合法
- 应用过滤器:允许外部传入
Predicate进行规则过滤 - 设置默认值:如果未指定
limitApp(限制来源),默认为default - 生成限流控制器:根据流控效果生成对应的
TrafficShapingController:DefaultController:直接拒绝WarmUpController:预热RateLimiterController:排队等待WarmUpRateLimiterController:预热 + 排队等待
- 按 groupFunction 分组:默认按资源名分组,也可以自定义分组函数
- 去重:使用
HashSet自动去重 - 可选排序:使用
FlowRuleComparator排序,确定规则生效顺序
2.3 规则动态更新机制
观察者模式的实现
Sentinel 使用观察者模式实现规则的动态更新,核心组件如下:
┌─────────────────────────────────────────────────────────────┐
│ 观察者模式架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 被观察者 │
│ ┌────────────────────────────────────┐ │
│ │ SentinelProperty<T> │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ - List<PropertyListener> │ │ │
│ │ │ - updateValue(T newValue) │ │ │
│ │ │ - listeners.configUpdate()│ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑ 注册 │
│ ┌────────────────────────────────────┐ │
│ │ 观察者 │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ PropertyListener<T> │ │ │
│ │ │ + configUpdate(T value) │ │ │
│ │ │ + configLoad(T value) │ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
具体实现:
- FlowRuleManager 持有 PropertyListener:
private static final FlowPropertyListener LISTENER = new FlowPropertyListener();
private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<>();
static {
currentProperty.addListener(LISTENER);
}
- 动态更新规则:
public static void loadRules(List<FlowRule> rules) {
currentProperty.updateValue(rules); // 触发观察者通知
}
- 观察者响应变化:
private static final class FlowPropertyListener implements PropertyListener<List<FlowRule>> {
@Override
public synchronized void configUpdate(List<FlowRule> value) {
Map<String, List<FlowRule>> rules = FlowRuleUtil.buildFlowRuleMap(value);
if (rules != null) {
flowRules.clear();
flowRules.putAll(rules);
}
RecordLog.info("[FlowRuleManager] Flow rules received: " + flowRules);
}
}
关于 “RuleLoader” 的说明
在参考的得物技术公众号文章中提到了 RuleLoader 类,但经过对 Sentinel 官方源码的全面搜索,未找到这个类。它可能是项目自行封装的工具类,并非 Sentinel 核心库的组成部分。
Sentinel 官方提供的规则加载方式是直接调用各个 *Manager.loadRules() 方法,或通过 datasource 扩展集成外部配置中心。
第三章:流量拦截 —— Filter、AOP 与拦截器的多重入口
Sentinel 需要在请求进入业务逻辑之前进行拦截,将请求引入 Sentinel 的处理流程。针对不同的应用场景,Sentinel 提供了三种拦截方式。
3.1 Servlet Filter 拦截
对于传统的 Web 应用,Sentinel 提供了 Servlet Filter 拦截器。主要有两个实现类:
- CommonFilter:较早的实现,功能相对简单
- SentinelServletFilter:较新的实现,功能更完善
工作流程
HTTP 请求
↓
SentinelServletFilter.doFilter()
↓
┌─────────────────────────────────────────────────────────┐
│ 1. 获取并清理请求路径 │
│ target = FilterUtil.filterTarget(request) │
│ │
│ 2. URL 清理(如果配置了 SentinelUrlCleaner) │
│ target = urlCleaner.clean(request, target) │
│ │
│ 3. 进入 Sentinel 上下文 │
│ ContextUtil.enter(contextName, origin) │
│ │
│ 4. 创建 Entry(资源名根据 httpMethodSpecify 配置) │
│ - true: "GET:/api/users" │
│ - false: "/api/users" │
│ urlEntry = SphU.entry(target, ...) │
│ │
│ 5. 执行后续过滤器(业务逻辑) │
│ chain.doFilter(request, response) │
│ │
│ 6. 处理异常 │
│ - BlockException: 调用 UrlBlockHandler │
│ - 其他异常: Tracer.traceEntry() │
│ │
│ 7. 释放资源 │
│ urlEntry.exit() │
│ ContextUtil.exit() │
└─────────────────────────────────────────────────────────┘
资源名生成规则
资源名的生成由 httpMethodSpecify 配置决定:
// WebServletConfigManager 配置
private static boolean httpMethodSpecify = false;
// 在 Filter 中
if (httpMethodSpecify) {
String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + ":" + target;
// 资源名:GET:/api/users
urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
} else {
// 资源名:/api/users
urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
}
选择建议:
- 如果同一个 URL 的不同 HTTP 方法需要不同的流控策略,设置为
true - 如果同一个 URL 的所有方法使用相同的流控策略,设置为
false(默认)
URL 清理逻辑
对于 RESTful API,URL 中通常包含动态参数(如 /api/users/123)。如果不进行清理,每个不同的用户 ID 都会被识别为不同的资源,导致资源数量爆炸。
SentinelUrlCleaner 接口定义:
public interface SentinelUrlCleaner {
/**
* 清理 URL,将包含动态参数的 URL 标准化
*
* @param request HTTP 请求
* @param originalUrl 原始 URL
* @return 清理后的 URL
*/
String clean(HttpServletRequest request, String originalUrl);
}
示例实现:
public class RestfulUrlCleaner implements SentinelUrlCleaner {
@Override
public String clean(HttpServletRequest request, String originalUrl) {
// 将 /api/users/123 清理为 /api/users/:id
// 将 /api/orders/abc-456/items/789 清理为 /api/orders/:id/items/:itemId
// ... 具体实现
}
}
// 注册清理器
WebCallbackManager.setUrlCleaner(new RestfulUrlCleaner());
配置示例:
// 在 Spring Boot 配置中
@Configuration
public class SentinelConfig {
@PostConstruct
public void initUrlCleaner() {
WebCallbackManager.setUrlCleaner(new RestfulUrlCleaner());
}
}
3.2 Spring MVC 拦截器
对于使用 Spring MVC 的应用,Sentinel 提供了 SentinelWebInterceptor 拦截器。
拦截器工作原理
SentinelWebInterceptor 实现了 Spring MVC 的 HandlerInterceptor 接口:
public class SentinelWebInterceptor extends AbstractSentinelInterceptor
implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
try {
String resourceName = createResourceName(request, handler);
return entry(request, response, handler, resourceName);
} catch (BlockException e) {
handleBlockException(request, response, e);
return false;
}
}
}
与 Filter 的区别与选择
| 特性 | Servlet Filter | Spring MVC 拦截器 |
|---|---|---|
| 执行时机 | 在 DispatcherServlet 之前 | 在 DispatcherServlet 之后 |
| 可访问的上下文 | 仅 Servlet API | 可访问 Spring MVC 上下文 |
| 适用场景 | 任何 Web 应用 | Spring MVC 应用 |
| 灵活性 | 较低 | 较高,可获取 Handler 信息 |
选择建议:
- 如果是纯 Servlet 应用或非 Spring MVC 框架,使用 Filter
- 如果是 Spring MVC 应用,优先使用拦截器(可以获取更多 Handler 信息)
3.3 AOP 切面拦截(@SentinelResource)
@SentinelResource 注解提供了最灵活的资源定义方式,可以精确控制某个方法的流控和降级逻辑。
注解参数详解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
/**
* 资源名称(必需)
*/
String value() default "";
/**
* 入口类型:IN(入站)或 OUT(出站)
*/
EntryType entryType() default EntryType.OUT;
/**
* 资源类型
*/
int resourceType() default 0;
/**
* BlockException 处理方法名称
*/
String blockHandler() default "";
/**
* blockHandler 所在的类
*/
Class<?>[] blockHandlerClass() default {};
/**
* fallback 方法名称
*/
String fallback() default "";
/**
* 默认 fallback 方法名称
*/
String defaultFallback() default "";
/**
* fallback 所在的类
*/
Class<?>[] fallbackClass() default {};
/**
* 需要追踪并触发 fallback 的异常类型
*/
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
/**
* 需要忽略的异常类型(不触发 fallback)
*/
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
SentinelResourceAspect 核心逻辑
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {
@Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {}
@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {
// 获取目标方法和注解
Method originMethod = resolveMethod(pjp);
SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
// 创建 Entry
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
// 执行原方法
Object result = pjp.proceed();
return result;
} catch (BlockException ex) {
// 处理限流/熔断异常
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
// 处理业务异常
// 1. 检查是否在忽略列表
if (exceptionBelongsTo(ex, annotation.exceptionsToIgnore())) {
throw ex;
}
// 2. 检查是否在追踪列表
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
blockHandler 与 fallback 的区别与使用
这是理解 @SentinelResource 的关键:
| 特性 | blockHandler | fallback |
|---|---|---|
| 处理异常类型 | BlockException(限流、熔断等) |
业务异常(Throwable) |
| 触发场景 | 被流控、熔断规则拦截 | 业务方法抛出异常 |
| 方法签名要求 | 原方法参数 + BlockException |
原方法参数或 原方法参数 + Throwable |
| 访问权限 | 需为 public | 需为 public |
| 是否支持外部类 | 是(需指定 blockHandlerClass) |
是(需指定 fallbackClass) |
| 外部类方法要求 | 必须是 static | 必须是 static |
示例代码:
@Service
public class UserService {
@SentinelResource(
value = "getUserById",
blockHandler = "handleBlock",
fallback = "handleFallback"
)
public User getUserById(Long id) {
// 业务逻辑
return userRepository.findById(id);
}
// blockHandler:原方法参数 + BlockException
public User handleBlock(Long id, BlockException ex) {
log.warn("getUserById 被限流, id={}", id);
return User.defaultUser(); // 返回默认用户
}
// fallback:原方法参数或 原方法参数 + Throwable
public User handleFallback(Long id, Throwable ex) {
log.error("getUserById 发生异常, id={}", id, ex);
return User.emptyUser(); // 返回空用户
}
}
3.4 三种方式的对比与选择建议
| 拦截方式 | 粒度 | 灵活性 | 配置复杂度 | 适用场景 |
|---|---|---|---|---|
| Servlet Filter | URL 级别 | 中 | 低 | 传统 Web 应用、需要统一拦截 |
| Spring MVC 拦截器 | Handler 级别 | 中 | 低 | Spring MVC 应用 |
| @SentinelResource | 方法级别 | 高 | 高 | 需要精细化控制的业务方法 |
选择建议:
- 优先使用 Filter/拦截器:对于常规的接口限流,使用 Filter 或拦截器,无需修改业务代码
- 核心接口使用 @SentinelResource:对于核心业务接口,使用注解可以:
- 自定义资源名(语义化)
- 提供更精细的降级逻辑
- 区分限流和业务异常的处理
- URL 清理别忘了:RESTful API 必须配置
SentinelUrlCleaner,避免资源数量爆炸
第四章:核心处理链 —— ProcessorSlotChain 责任链
当请求通过拦截进入 Sentinel 后,会经过一条精心设计的责任链——ProcessorSlotChain。这是 Sentinel 最核心的架构设计之一。
4.1 责任链模式的实现
SPI 机制加载 Slot
Sentinel 使用 SPI(Service Provider Interface)机制动态加载 Slot,实现高度可扩展的架构。
SPI 配置文件位置:
sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot
SPI 配置文件内容:
com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot
com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot
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.block.flow.FlowSlot
com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot
Slot 的顺序定义:
每个 Slot 通过 @Spi 注解定义顺序:
@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT)
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
// order = -10000
}
SlotChain 的构建与缓存
SlotChainProvider 提供链的创建:
public final class SlotChainProvider {
private static SlotChainBuilder chainBuilder = null;
public static ProcessorSlotChain newSlotChain() {
if (chainBuilder != null) {
return chainBuilder.build();
}
// 通过 SPI 加载 SlotChainBuilder
chainBuilder = SpiLoader.of(SlotChainBuilder.class)
.loadFirstInstanceOrDefault();
if (chainBuilder == null) {
chainBuilder = new DefaultSlotChainBuilder();
}
return chainBuilder.build();
}
}
DefaultSlotChainBuilder 构建链:
@Spi(isDefault = true)
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
// 通过 SPI 加载所有 ProcessorSlot 并按 order 排序
List<ProcessorSlot> sortedSlotList = SpiLoader.of(ProcessorSlot.class)
.loadInstanceListSorted();
for (ProcessorSlot slot : sortedSlotList) {
if (!(slot instanceof AbstractLinkedProcessorSlot)) {
continue;
}
// 添加到链表末尾
chain.addLast((AbstractLinkedProcessorSlot<?>) slot);
}
return chain;
}
}
SlotChain 的缓存:
每个资源对应一个 SlotChain,缓存在 CtSph.chainMap 中:
// CtSph.java
private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
= new HashMap<ResourceWrapper, ProcessorSlotChain>();
ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
ProcessorSlotChain chain = chainMap.get(resourceWrapper);
if (chain == null) {
synchronized (LOCK) {
chain = chainMap.get(resourceWrapper);
if (chain == null) {
if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
return null;
}
chain = SlotChainProvider.newSlotChain();
Map<ResourceWrapper, ProcessorSlotChain> newMap =
new HashMap<>(chainMap.size() + 1);
newMap.putAll(chainMap);
newMap.put(resourceWrapper, chain);
chainMap = newMap; // 不可变更新
}
}
}
return chain;
}
4.2 各 Slot 的职责与执行顺序(基于 1.8.x 版本)
以下是 Sentinel 1.8.x 中各个 Slot 的完整列表和执行顺序:
| Slot | Order 值 | 说明 | 核心职责 |
|---|---|---|---|
| NodeSelectorSlot | -10000 | 节点选择器 | 为不同上下文创建 DefaultNode,构建调用树 |
| ClusterBuilderSlot | -9000 | 集群节点构建 | 创建 ClusterNode 聚合所有上下文的统计 |
| LogSlot | -8000 | 日志记录 | 记录阻塞异常日志 |
| StatisticSlot | -7000 | 统计收集 | 收集运行时指标(QPS、RT、线程数) |
| AuthoritySlot | -6000 | 权限控制 | 黑白名单检查 |
| SystemSlot | -5000 | 系统保护 | CPU、负载、RT 检查 |
| ParamFlowSlot | -3000 | 参数流控 | 位于扩展模块,需单独启用 |
| FlowSlot | -2000 | 流量控制 | QPS/线程数流控判断 |
| DegradeSlot | -1000 | 熔断降级 | 熔断规则检查,已整合现代 CircuitBreaker |
注意:ParamFlowSlot 不在默认 SPI 配置中,位于 sentinel-extension/sentinel-parameter-flow-control 模块,需要单独启用参数流控功能。
4.3 关键 Slot 深度解析
NodeSelectorSlot - 上下文节点选择
核心职责:为每个资源在不同上下文中创建对应的 DefaultNode,构建调用树。
并发保护:使用 volatile + 双重检查锁:
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> {
// volatile 保证可见性
private volatile Map<String, DefaultNode> map = new HashMap<>(10);
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count,
boolean prioritized, Object... args) throws Throwable {
// 获取当前上下文对应的节点
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<String, DefaultNode> cacheMap = new HashMap<>(map.size());
cacheMap.putAll(map);
cacheMap.put(context.getName(), node);
map = cacheMap;
// 构建调用树
((DefaultNode) context.getLastNode()).addChild(node);
}
}
}
// 设置当前上下文的当前节点
context.setCurNode(node);
// 继续执行后续 Slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
}
DefaultNode 的创建与缓存机制:
- 每个资源在每个上下文中有一个 DefaultNode
- 使用 volatile + 双重检查锁保证线程安全
- 使用写时复制(Copy-on-Write)更新缓存
ClusterBuilderSlot - 集群节点构建
核心职责:创建 ClusterNode,用于聚合所有上下文中该资源的统计数据。
DefaultNode vs ClusterNode 的区别:
| 特性 | DefaultNode | ClusterNode |
|---|---|---|
| 作用范围 | 特定于上下文(context-specific) | 全局(context-independent) |
| 统计范围 | 只统计当前上下文中的调用 | 统计所有上下文中的调用 |
| 用途 | 用于分析特定调用链路的流量 | 用于分析资源的整体流量 |
public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
private static volatile Map<ResourceWrapper, ClusterNode> 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 {
// 创建或获取 ClusterNode
if (clusterNode == null) {
synchronized (lock) {
if (clusterNode == null) {
clusterNode = new ClusterNode(resourceWrapper.getName(),
resourceWrapper.getResourceType());
// 更新全局映射表
HashMap<ResourceWrapper, ClusterNode> newMap =
new HashMap<>(Math.max(clusterNodeMap.size(), 16));
newMap.putAll(clusterNodeMap);
newMap.put(node.getId(), clusterNode);
clusterNodeMap = newMap;
}
}
}
// 将 ClusterNode 设置到 DefaultNode 中
node.setClusterNode(clusterNode);
// 如果有来源标识,创建 OriginNode
if (!"".equals(context.getOrigin())) {
Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
context.getCurEntry().setOriginNode(originNode);
}
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
}
OriginNode 的创建逻辑:
- 当调用
ContextUtil.enter(contextName, origin)时指定了来源 - Sentinel 会为每个来源创建一个 OriginNode
- 用于按来源统计和限流
StatisticSlot - 统计插槽
核心职责:收集各种运行时指标,是流控决策的数据基础。
entry 时的统计逻辑:
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
try {
// 先执行后续 Slot,完成所有规则检查
fireEntry(context, resourceWrapper, node, count, prioritized, args);
// 代码运行到这里,说明所有规则检查都通过了
// 开始统计通过的数据
// 1. 增加线程数
node.increaseThreadNum();
if (context.getCurEntry().getOriginNode() != null) {
context.getCurEntry().getOriginNode().increaseThreadNum();
}
if (resourceWrapper.getEntryType() == EntryType.IN) {
Constants.ENTRY_NODE.increaseThreadNum();
}
// 2. 增加通过请求数
node.addPassRequest(count);
// ... 省略其他统计逻辑
} catch (PriorityWaitException ex) {
// 优先级等待异常,增加线程数但不增加通过数
node.increaseThreadNum();
// ...
throw ex;
} catch (BlockException e) {
// 被限流,记录被限流数
// ...
throw e;
} catch (Throwable ex) {
// 业务异常,记录异常数
// ...
throw ex;
}
}
}
exit 时的统计逻辑:
@Override
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
// 如果没有异常,记录 success 和 RT
node.addSuccessRequest(count);
node.addResponseTime(context.getCurEntry().getRt());
// 线程数 -1
node.decreaseThreadNum();
// ...
}
记录数据的维度:
- 线程数:
+1(entry) /-1(exit) - 记录当前 DefaultNode 数据
- 记录对应的 OriginNode 数据(若存在 origin)
- 累计 IN 统计数据(若流量类型为 IN)
DegradeSlot - 熔断降级
核心职责:根据熔断规则判断是否需要熔断。
实现机制:
DegradeSlot 通过 CircuitBreaker 接口执行熔断检查。
public class DegradeSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
@Override
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
boolean prioritized, Object... args) throws Throwable {
// 执行熔断检查
performChecking(context, resourceWrapper, node, count);
// 继续执行后续 Slot
fireEntry(context, resourceWrapper, node, count, prioritized, args);
}
void performChecking(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count) {
// 获取该资源的所有熔断器
List<CircuitBreaker> circuitBreakers = DegradeRuleManager.getCircuitBreakers(resourceWrapper.getName());
if (circuitBreakers == null || circuitBreakers.isEmpty()) {
return;
}
for (CircuitBreaker cb : circuitBreakers) {
// 尝试通过请求
if (!cb.tryPass(context)) {
throw new DegradeException(cb.getRule(), cb.getCurrentState());
}
}
}
}
熔断器实现:
Sentinel 提供了两种熔断器实现:
- ExceptionCircuitBreaker:异常比例/异常数熔断
- ResponseTimeCircuitBreaker:慢调用比例熔断
// CircuitBreaker 接口
public interface CircuitBreaker {
/**
* 尝试通过请求
* @return true 表示可以通过,false 表示应该被拒绝
*/
boolean tryPass(Context context);
/**
* 获取当前状态
*/
State getCurrentState();
/**
* 获取关联的规则
*/
DegradeRule getRule();
}
// 熔断器状态
public enum State {
OPEN, // 打开:熔断中,请求直接拒绝
HALF_OPEN, // 半开:试探性恢复
CLOSED // 关闭:正常状态
}
4.4 执行流程图
SphU.entry(resourceName)
↓
CtSph.entryWithPriority()
↓
获取或创建 ProcessorSlotChain
↓
chain.entry(context, ...)
↓
┌──────────────────────────────────────┐
│ ProcessorSlotChain │
│ ┌───────────────────────────────┐ │
│ │ NodeSelectorSlot │ │
│ │ - 创建/获取 DefaultNode │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ ClusterBuilderSlot │ │
│ │ - 创建/获取 ClusterNode │ │
│ │ - 创建 OriginNode(如有) │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ LogSlot │ │
│ │ - 记录日志 │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ StatisticSlot │ │
│ │ - 先执行后续 Slot │ │
│ │ - 根据结果统计数据 │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ AuthoritySlot │ │
│ │ - 黑白名单检查 │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ SystemSlot │ │
│ │ - CPU/负载/RT 检查 │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ FlowSlot │ │
│ │ - 流控规则检查 │ │
│ │ - 抛出 FlowException │ │
│ │ ↓ fireEntry() │ │
│ ├───────────────────────────────┤ │
│ │ DegradeSlot │ │
│ │ - 熔断规则检查 │ │
│ │ - 抛出 DegradeException │ │
│ └───────────────────────────────┘ │
└──────────────────────────────────────┘
↓
如果没有 BlockException
↓
业务逻辑执行
4.5 ParamFlowSlot 的特殊说明
ParamFlowSlot 是一个特殊的 Slot,与其他 Slot 不同:
- 位于扩展模块:
sentinel-extension/sentinel-parameter-flow-control - 不在默认 SPI 配置中:需要单独添加依赖才能使用
- Order 值为 -3000:位于
FlowSlot(-2000) 之前
@Spi(order = -3000)
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<Object> {
// 参数流控的实现
}
启用方式:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.6</version>
</dependency>
使用场景:
- 需要根据请求参数进行精细化流控
- 如:对同一个接口,不同用户的限流阈值不同
- 识别热点参数并限流
第五章:数据统计 —— 滑动窗口算法
数据统计是流控决策的基础。Sentinel 使用精心设计的滑动窗口算法来高效、准确地收集各种运行时指标。
5.1 滑动窗口的核心思想
滑动窗口是一种用于统计时间序列数据的算法,核心思想包括:
- 环形数组在时间轴上的滚动:使用固定大小的数组,循环使用每个位置
- 时间窗口的概念:将时间轴划分为固定长度的时间窗口
- 窗口的复用:时间窗口过期后,位置会被新的窗口复用
图解:
时间轴: 0ms 500ms 1000ms 1500ms 2000ms 2500ms 3000ms
│ │ │ │ │ │ │
数组索引: [0] [1] [0] [1] [0] [1] [0]
│ │ │ │ │ │ │
窗口: W0 W1 W2 W3 W4 W5 W6
数组长度 = 2,窗口长度 = 500ms,总周期 = 1000ms
当时间移动时,窗口位置循环使用:
- t=0ms: 使用 array[0],窗口开始时间 0ms
- t=500ms: 使用 array[1],窗口开始时间 500ms
- t=1000ms: 使用 array[0],窗口开始时间 1000ms(复用)
- t=1500ms: 使用 array[1],窗口开始时间 1500ms(复用)
...
5.2 时间桶结构(WindowWrap)
每个时间窗口被封装成一个 WindowWrap 对象:
public class WindowWrap<T> {
/**
* 窗口长度(毫秒)
*/
private final long windowLengthInMs;
/**
* 窗口开始时间戳
* 它是 windowLengthInMs 的整数倍
*/
private long windowStart;
/**
* 窗口数据(泛型 T)
* Sentinel 中通常是 MetricBucket,存储统计数据
*/
private T value;
public WindowWrap(long windowLengthInMs, long windowStart, T value) {
this.windowLengthInMs = windowLengthInMs;
this.windowStart = windowStart;
this.value = value;
}
}
三个关键字段:
windowLengthInMs:窗口大小(如 500ms、1000ms)windowStart:窗口开始时间戳value:窗口数据,在 Sentinel 中通常是MetricBucket
5.3 统计事件(MetricEvent)
MetricBucket 存储各种统计事件的计数,每个事件对应一个 LongAdder:
public enum MetricEvent {
PASS, // 通过的请求
BLOCK, // 被限流的请求
SUCCESS, // 成功的请求
EXCEPTION, // 异常的请求
RT, // 响应时间
OCCUPIED_PASS // 抢占的通过(借用未来配额)
}
public class MetricBucket {
/**
* 计数器数组,长度是事件类型数
* LongAdder 是线程安全的计数器,性能优于 AtomicLong
*/
private final LongAdder[] counters;
public MetricBucket() {
// 初始化计数器数组
this.counters = new LongAdder[MetricEvent.values().length];
for (MetricEvent event : MetricEvent.values()) {
counters[event.ordinal()] = new LongAdder();
}
}
public MetricBucket add(MetricEvent event, long n) {
counters[event.ordinal()].add(n);
return this;
}
public long get(MetricEvent event) {
return counters[event.ordinal()].sum();
}
}
5.4 两种实现对比
Sentinel 提供了两种滑动窗口实现,各有不同的适用场景。
BucketLeapArray(普通滑动窗口)
结构与工作原理:
public class BucketLeapArray extends LeapArray<MetricBucket> {
public BucketLeapArray(int sampleCount, int intervalInMs) {
super(sampleCount, intervalInMs);
}
@Override
public MetricBucket newEmptyBucket(long time) {
return new MetricBucket();
}
@Override
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long time) {
// 更新开始时间并重置值
w.resetTo(time);
w.value().reset();
return w;
}
}
核心特点:
- 每个时间桶独立记录该时间窗口内的指标数据
- 窗口过期后直接重置,不保留任何数据
适用场景:秒级统计(rollingCounterInSecond)
数据精度限制:0-50% 的统计误差
为什么会有误差?
得物技术的原文中有一个例子,假设 QPS 稳定为 10,请求均匀分布:
- t=0ms-1000ms:通过 10 个请求
- t=1000ms:第一个窗口重置,第二个窗口有值
- t=1001ms:观察到的 QPS 可能只有 5(因为第一个窗口被重置了)
这就是 0-50% 误差的来源。
OccupiableBucketLeapArray(可抢占滑动窗口)
双数组设计:
public class OccupiableBucketLeapArray extends LeapArray<MetricBucket> {
/**
* 借用未来窗口的数组
*/
private final FutureBucketLeapArray borrowArray;
public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) {
super(sampleCount, intervalInMs);
this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs);
}
@Override
public MetricBucket newEmptyBucket(long time) {
MetricBucket newBucket = new MetricBucket();
// 从 borrowArray 中借用数据
MetricBucket borrowBucket = borrowArray.getWindowValue(time);
if (borrowBucket != null) {
newBucket.reset(borrowBucket); // 复制借用数据
}
return newBucket;
}
@Override
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long time) {
w.resetTo(time);
MetricBucket borrowBucket = borrowArray.getWindowValue(time);
if (borrowBucket != null) {
w.value().reset();
w.value().addPass((int) borrowBucket.pass()); // 添加借用的 pass 数据
} else {
w.value().reset();
}
return w;
}
@Override
public long currentWaiting() {
borrowArray.currentWindow();
long currentWaiting = 0;
List<MetricBucket> list = borrowArray.values();
for (MetricBucket window : list) {
currentWaiting += window.pass();
}
return currentWaiting;
}
@Override
public void addWaiting(long time, int acquireCount) {
WindowWrap<MetricBucket> window = borrowArray.currentWindow(time);
window.value().add(MetricEvent.PASS, acquireCount);
}
}
借用未来窗口的配额机制:
- 新桶创建时:从
borrowArray复制数据 - 窗口重置时:添加借用的 pass 数据
- 添加等待时:向
borrowArray添加记录 - 计算等待数:遍历
borrowArray统计
性能与精度的权衡:
| 特性 | 普通滑动窗口 | 可抢占滑动窗口 |
|---|---|---|
| 统计精度 | 有 0-50% 误差 | 更精确,平滑过渡 |
| 性能开销 | 较低 | 较高(双数组) |
| 存储空间 | 较小 | 较大(双数组) |
| 适用场景 | 一般流控 | 排队等待策略 |
适用场景:排队等待的流控策略(RateLimiterController)
5.5 统计维度
Sentinel 提供两个时间维度的统计:
秒级统计(rollingCounterInSecond)
private transient volatile Metric rollingCounterInSecond =
new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL);
配置:
- 窗口数量:2 个
- 每个窗口:500ms
- 总周期:1000ms(1 秒)
适用场景:实时流控判断
分钟级统计(rollingCounterInMinute)
private transient volatile Metric rollingCounterInMinute =
new ArrayMetric(60, 60 * 1000, false);
配置:
- 窗口数量:60 个
- 每个窗口:1000ms(1 秒)
- 总周期:60000ms(60 秒)
适用场景:监控面板展示、趋势分析
5.6 数据结构层次
Sentinel 的统计数据结构形成了一个层次化的节点树:
machine-root (EntranceNode)
│
└── sentinel_web_servlet_context (EntranceNode)
│
├── /api/users (DefaultNode) → ClusterNode (全局聚合)
│ │
│ └── OriginNode (按来源统计)
│
├── /api/orders (DefaultNode) → ClusterNode
│ │
│ └── OriginNode
│
└── ...
节点类型说明:
| 节点类型 | 作用域 | 说明 |
|---|---|---|
EntranceNode |
入口节点 | 代表一个上下文的入口,如 sentinel_web_servlet_context |
DefaultNode |
上下文特定 | 特定上下文中某个资源的统计节点 |
ClusterNode |
全局聚合 | 聚合所有上下文中某个资源的统计数据 |
OriginNode |
来源特定 | 按调用来源统计的节点 |
StatisticNode |
核心统计 | 包含秒级和分钟级统计的核心实现 |
第六章:流控规则判断 —— FlowSlot 深度解析
FlowSlot 是 Sentinel 中最常用的 Slot,负责根据流控规则判断是否限流。
6.1 流控规则的三大要素
每条流控规则由三个核心要素构成:
grade(阈值类型)
/**
* 流控的阈值类型
* 0: 线程数
* 1: QPS
*/
private int grade = RuleConstant.FLOW_GRADE_QPS;
- FLOW_GRADE_THREAD(0):按并发线程数限流
- FLOW_GRADE_QPS(1):按 QPS 限流
strategy(调用关系策略)
/**
* 基于调用链的流控策略
*/
private int strategy = RuleConstant.STRATEGY_DIRECT;
- STRATEGY_DIRECT(0):直接流控(按来源)
- STRATEGY_RELATE(1):关联流控(关联资源)
- STRATEGY_CHAIN(2):链路流控(按入口资源)
controlBehavior(流控效果)
/**
* 流控效果(流量整形行为)
*/
private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;
- CONTROL_BEHAVIOR_DEFAULT(0):直接拒绝(Fast Fail)
- CONTROL_BEHAVIOR_WARM_UP(1):预热(Warm Up)
- CONTROL_BEHAVIOR_RATE_LIMITER(2):排队等待(Rate Limiter)
- CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER(3):预热 + 排队等待
6.2 流控规则的生效顺序
当一个资源配置了多条流控规则时,规则的生效顺序由 FlowRuleComparator 决定:
public class FlowRuleComparator implements Comparator<FlowRule> {
@Override
public int compare(FlowRule o1, FlowRule o2) {
// 1. 集群模式规则排在最后
if (o1.isClusterMode() && !o2.isClusterMode()) {
return 1;
}
if (!o1.isClusterMode() && o2.isClusterMode()) {
return -1;
}
// 2. 默认限制应用(LIMIT_APP_DEFAULT)优先级较高
if (RuleConstant.LIMIT_APP_DEFAULT.equals(o1.getLimitApp())
&& !RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) {
return -1;
}
if (!RuleConstant.LIMIT_APP_DEFAULT.equals(o1.getLimitApp())
&& RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) {
return 1;
}
// 3. 其他规则按配置顺序
return 0;
}
}
执行逻辑:
public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
Collection<FlowRule> 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);
}
}
}
}
6.3 流控效果的实现
不同的流控效果对应不同的 TrafficShapingController 实现:
直接拒绝(DefaultController)
public class DefaultController implements TrafficShapingController {
private final int grade;
private final double count;
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// 获取当前计数
int curCount = node.grade == RuleConstant.FLOW_GRADE_QPS
? (int) node.passQps()
: (int) node.curThreadNum();
// 判断是否超过阈值
if (curCount + acquireCount > count) {
return false; // 拒绝
}
return true; // 通过
}
}
特点:
- 快速失败,不排队
- 适用于不需要排队等待的场景
预热(WarmUpController)
public class WarmUpController implements TrafficShapingController {
// 冷启动因子,默认为 3
private static final int DEFAULT_WARM_UP_FACTOR = 3;
private final double count;
private final int warmUpPeriodSec;
private final double coldFactor;
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// 计算预热状态
// 阈值从 count / coldFactor 逐渐上升到 count
// ...
}
}
预热过程:
阈值
↑
│ ┌───────────── count
│ /
│ /
│ /
│ /
│ └───────────────── count / coldFactor
│
└──────────────────────────────→ 时间
warmUpPeriodSec
特点:
- 从较低的阈值(
count / coldFactor)开始,逐渐上升到配置的阈值 - 默认冷启动因子为 3
- 适用于需要预热的服务(如缓存、数据库连接)
排队等待(RateLimiterController)
public class RateLimiterController implements TrafficShapingController {
private final double maxQueueingTimeMs;
private final double count;
@Override
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
// 计算请求通过的时间间隔
long passTime = calculatePassTime(acquireCount);
long currentTime = TimeUtil.currentTimeMillis();
// 如果预计通过时间在可接受范围内,等待
if (passTime - currentTime < maxQueueingTimeMs) {
// 等待...
return true;
}
return false; // 超时,拒绝
}
}
与可抢占窗口的配合:
排队等待策略需要使用 OccupiableBucketLeapArray,因为:
- 需要借用未来窗口的配额
- 需要计算当前等待中的请求数量
特点:
- 匀速排队,请求间隔固定
- 适用于消息队列处理、削峰填谷
6.4 QPS vs 线程数限流的区别
| 特性 | QPS 限流 | 线程数限流 |
|---|---|---|
| 限流依据 | 每秒请求数 | 并发线程数 |
| 统计方式 | 滑动窗口统计 | 原子计数器 |
| 适用场景 | 请求处理时间短 | 请求处理时间长 |
| 限流效果 | 快速失败 | 控制并发量 |
| Controller | DefaultController 等 | DefaultController |
选择建议:
- 如果请求处理时间短且稳定,使用 QPS 限流
- 如果请求处理时间长或需要控制并发量,使用线程数限流
第七章:实战建议
7.1 如何选择滑动窗口类型
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 一般流控 | BucketLeapArray |
性能好,资源占用小 |
| 排队等待策略 | OccupiableBucketLeapArray |
需要借用未来配额 |
| 精确统计 | OccupiableBucketLeapArray |
减少统计误差 |
7.2 如何合理配置流控规则
grade(阈值类型)选择:
- 请求处理时间短(< 100ms):使用 QPS 限流
- 请求处理时间长(> 500ms):使用线程数限流
strategy(调用关系策略)选择:
- 一般场景:
STRATEGY_DIRECT(直接流控) - 关联资源保护:
STRATEGY_RELATE(关联流控) - 链路入口流控:
STRATEGY_CHAIN(链路流控)
controlBehavior(流控效果)选择:
- 不需要排队:
CONTROL_BEHAVIOR_DEFAULT(直接拒绝) - 需要预热:
CONTROL_BEHAVIOR_WARM_UP(预热) - 削峰填谷:
CONTROL_BEHAVIOR_RATE_LIMITER(排队等待)
7.3 源码阅读路径建议
对于想要深入源码的读者,建议按以下顺序阅读:
- 核心入口:
CtSph.entryWithPriority() - 责任链构建:
DefaultSlotChainBuilder.build() - 统计收集:
StatisticSlot的entry()和exit() - 滑动窗口:
LeapArray.currentWindow() - 流控判断:
FlowSlot.checkFlow() - 规则加载:
FlowRuleUtil.buildFlowRuleMap()
【总结】
Sentinel 核心设计思想回顾
通过对 Sentinel 源码的深入分析,我们可以总结出以下核心设计思想:
-
责任链模式:通过 ProcessorSlotChain 将复杂的流控逻辑分解为多个独立的 Slot
-
观察者模式:实现规则的动态更新,支持热配置
-
滑动窗口算法:高效、准确地收集运行时指标
-
SPI 机制:提供高度可扩展的架构
从源码学习的方法论
sentinel并不是第一次学习了,但是这次阅读源码不仅仅是理解代码,更重要的是:
- 抽象提炼:从具体代码中抽象出设计思想和模式
- 理解权衡:理解为什么选择这种方案,而不是其他方案
- 验证假设:通过实际运行验证自己的理解
- 举一反三:将学到的方法应用到其他项目中
避免的做法:
- 简单地复制粘贴代码
- 只看代码不看注释
- 不动手验证就下结论
参考资料
- Sentinel 官方文档:https://sentinelguard.io/zh-cn/
- Sentinel GitHub 仓库:https://github.com/alibaba/Sentinel
- 得物技术公众号原文:Sentinel Java客户端限流原理解析
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)