基于 Sentinel 1.8.x 版本源码深度剖析

说明:本文参考了得物技术公众号《Sentinel Java客户端限流原理解析》的文章结构,结合 Sentinel 官方源码进行深度学习和研究。本文所有技术描述均经过源码验证,旨在通过源码分析深入理解 Sentinel 的核心机制。


【引言】为什么需要理解 Sentinel 的底层机制?

在微服务架构日益普及的今天,服务间的调用关系变得错综复杂。当一个热门商品上线、一场促销活动开始,或者某个第三方服务突然响应缓慢时,我们的系统能否经受住流量的冲击?

实际场景引入

想象一下这样的场景:你的电商系统在"双11"大促期间,某个核心接口突然收到平时的 10 倍流量请求。如果没有有效的流量保护机制:

  • 数据库连接池迅速耗尽
  • 服务线程池被阻塞请求占满
  • 依赖服务因为大量请求而崩溃
  • 最终导致整个服务雪崩,业务全面中断

Sentinel 的价值

Sentinel 是阿里开源的、面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制熔断降级系统负载保护等多个维度来帮助您保障微服务的稳定性。

但本文不是一篇 API 使用指南。市面上已经有很多关于 Sentinel 如何使用的教程,而很少有人深入探讨它底层的架构设计思想和工作原理,本着学习与分享的精神来一起探索下底层吧。

本文的阅读收益

通过阅读本文,你将:

  1. 理解架构设计思想:掌握 Sentinel 的整体架构,理解它如何通过责任链模式、观察者模式等设计模式组织复杂的流量控制逻辑
  2. 掌握模块划分:深入了解 Sentinel 的模块划分,知道每个模块负责什么功能,以及它们之间如何协作
  3. 理解工作原理:从源码层面理解请求是如何被拦截、规则是如何被加载、流量是如何被统计、限流判断是如何执行的
  4. 提升源码阅读能力:学习如何从源码中抽象提炼核心思想,而不是简单地复制粘贴

版本说明

本文基于 Sentinel 1.8.x 版本进行分析。


第一章:Sentinel 整体架构全景图

1.1 功能划分

从功能角度,Sentinel 可以划分为以下几个核心模块:

规则管理模块

规则管理模块负责各种流量控制规则的加载、存储和更新,包括:

  • 流控规则(FlowRule):控制 QPS 或线程数阈值
  • 熔断规则(DegradeRule):当检测到资源响应时间过长或异常比例过高时,对资源进行熔断
  • 系统规则(SystemRule):保护整个系统,如 CPU 使用率、平均 RT 等
  • 授权规则(AuthorityRule):黑白名单控制,限制某些来源的访问
  • 参数流控规则(ParamFlowRule):针对请求参数进行精细化流控
  • 热点参数规则:识别热点参数并进行限流
流量拦截模块

流量拦截模块负责在请求进入业务逻辑之前进行拦截,将请求引入 Sentinel 的处理流程。主要拦截方式包括:

  • Servlet Filter 拦截:通过 CommonFilterSentinelServletFilter 拦截 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 步详解:

  1. 验证规则有效性:检查资源名、阈值、阈值类型等字段是否合法
  2. 应用过滤器:允许外部传入 Predicate 进行规则过滤
  3. 设置默认值:如果未指定 limitApp(限制来源),默认为 default
  4. 生成限流控制器:根据流控效果生成对应的 TrafficShapingController
    • DefaultController:直接拒绝
    • WarmUpController:预热
    • RateLimiterController:排队等待
    • WarmUpRateLimiterController:预热 + 排队等待
  5. 按 groupFunction 分组:默认按资源名分组,也可以自定义分组函数
  6. 去重:使用 HashSet 自动去重
  7. 可选排序:使用 FlowRuleComparator 排序,确定规则生效顺序

2.3 规则动态更新机制

观察者模式的实现

Sentinel 使用观察者模式实现规则的动态更新,核心组件如下:

┌─────────────────────────────────────────────────────────────┐
│                    观察者模式架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   被观察者                                                    │
│   ┌────────────────────────────────────┐                   │
│   │  SentinelProperty<T>                │                   │
│   │  ┌────────────────────────────┐    │                   │
│   │  │ - List<PropertyListener>    │    │                   │
│   │  │ - updateValue(T newValue)   │    │                   │
│   │  │   - listeners.configUpdate()│    │                   │
│   │  └────────────────────────────┘    │                   │
│   └────────────────────────────────────┘                   │
│              ↑ 注册                                           │
│   ┌────────────────────────────────────┐                   │
│   │  观察者                              │                   │
│   │  ┌────────────────────────────┐    │                   │
│   │  │ PropertyListener<T>         │    │                   │
│   │  │ + configUpdate(T value)     │    │                   │
│   │  │ + configLoad(T value)       │    │                   │
│   │  └────────────────────────────┘    │                   │
│   └────────────────────────────────────┘                   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

具体实现:

  1. FlowRuleManager 持有 PropertyListener
private static final FlowPropertyListener LISTENER = new FlowPropertyListener();
private static SentinelProperty<List<FlowRule>> currentProperty = new DynamicSentinelProperty<>();

static {
    currentProperty.addListener(LISTENER);
}
  1. 动态更新规则
public static void loadRules(List<FlowRule> rules) {
    currentProperty.updateValue(rules);  // 触发观察者通知
}
  1. 观察者响应变化
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 方法级别 需要精细化控制的业务方法

选择建议

  1. 优先使用 Filter/拦截器:对于常规的接口限流,使用 Filter 或拦截器,无需修改业务代码
  2. 核心接口使用 @SentinelResource:对于核心业务接口,使用注解可以:
    • 自定义资源名(语义化)
    • 提供更精细的降级逻辑
    • 区分限流和业务异常的处理
  3. 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. 线程数:+1(entry) / -1(exit)
  2. 记录当前 DefaultNode 数据
  3. 记录对应的 OriginNode 数据(若存在 origin)
  4. 累计 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 提供了两种熔断器实现:

  1. ExceptionCircuitBreaker:异常比例/异常数熔断
  2. 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 不同:

  1. 位于扩展模块sentinel-extension/sentinel-parameter-flow-control
  2. 不在默认 SPI 配置中:需要单独添加依赖才能使用
  3. 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 滑动窗口的核心思想

滑动窗口是一种用于统计时间序列数据的算法,核心思想包括:

  1. 环形数组在时间轴上的滚动:使用固定大小的数组,循环使用每个位置
  2. 时间窗口的概念:将时间轴划分为固定长度的时间窗口
  3. 窗口的复用:时间窗口过期后,位置会被新的窗口复用

图解

时间轴:   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);
    }
}

借用未来窗口的配额机制

  1. 新桶创建时:从 borrowArray 复制数据
  2. 窗口重置时:添加借用的 pass 数据
  3. 添加等待时:向 borrowArray 添加记录
  4. 计算等待数:遍历 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,因为:

  1. 需要借用未来窗口的配额
  2. 需要计算当前等待中的请求数量

特点

  • 匀速排队,请求间隔固定
  • 适用于消息队列处理、削峰填谷

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 源码阅读路径建议

对于想要深入源码的读者,建议按以下顺序阅读:

  1. 核心入口CtSph.entryWithPriority()
  2. 责任链构建DefaultSlotChainBuilder.build()
  3. 统计收集StatisticSlotentry()exit()
  4. 滑动窗口LeapArray.currentWindow()
  5. 流控判断FlowSlot.checkFlow()
  6. 规则加载FlowRuleUtil.buildFlowRuleMap()

【总结】

Sentinel 核心设计思想回顾

通过对 Sentinel 源码的深入分析,我们可以总结出以下核心设计思想:

  1. 责任链模式:通过 ProcessorSlotChain 将复杂的流控逻辑分解为多个独立的 Slot

  2. 观察者模式:实现规则的动态更新,支持热配置

  3. 滑动窗口算法:高效、准确地收集运行时指标

  4. SPI 机制:提供高度可扩展的架构

从源码学习的方法论

sentinel并不是第一次学习了,但是这次阅读源码不仅仅是理解代码,更重要的是:

  1. 抽象提炼:从具体代码中抽象出设计思想和模式
  2. 理解权衡:理解为什么选择这种方案,而不是其他方案
  3. 验证假设:通过实际运行验证自己的理解
  4. 举一反三:将学到的方法应用到其他项目中

避免的做法

  • 简单地复制粘贴代码
  • 只看代码不看注释
  • 不动手验证就下结论

参考资料

  1. Sentinel 官方文档:https://sentinelguard.io/zh-cn/
  2. Sentinel GitHub 仓库:https://github.com/alibaba/Sentinel
  3. 得物技术公众号原文Sentinel Java客户端限流原理解析
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐