Skip to content

Sentinel内部原理与生产治理

Sentinel 不只是“加一个注解后配置 QPS”。它在每个应用实例内部维护资源、调用上下文、调用树和滑动窗口统计,再通过一条可扩展的 SlotChain 判断请求能否进入业务。Dashboard 和 Nacos 等配置中心主要处在控制面;真正对每次请求做判断的是应用进程中的 Sentinel 数据面。

本文以企业中广泛使用的 Sentinel 1.8.x 核心模型讲原理,同时说明 JDK 8/Spring Boot 2 存量线与 Java 17+/Spring Boot 3 现代线。精确依赖和属性必须服从当前 Spring Cloud Alibaba BOM;不要把某个版本的 Starter 配置复制到另一条发布列车。

一、学完必须回答什么

  1. SphUCtSphContextEntryResourceWrapperDefaultNodeClusterNode 分别是什么。
  2. 一次请求从 entry() 到业务方法、再到 exit() 的完整过程。
  3. LeapArray 为什么只用固定数量桶就能维护滑动窗口。
  4. QPS、并发数、Warm Up、匀速排队和热点参数限流的算法边界。
  5. 熔断器怎样从 CLOSED 进入 OPEN、再由一次探测进入 HALF_OPEN。
  6. Dashboard、客户端命令端口、配置中心和本地 RuleManager 怎样协作。
  7. 本地流控与集群流控有什么本质区别,Token Server 挂了怎么办。
  8. 业务异常为什么可能没有计入熔断统计,异步调用为什么容易丢上下文。
  9. 扩容后阈值怎样变化,规则误发布、控制台失联和大量 Block 怎样恢复。

二、版本与生态边界

技术线常见环境学习重点
存量维护线JDK 8、Boot 2.x、匹配的Spring Cloud AlibabaSentinel 1.8.x Core、旧版Starter配置、Feign与Dashboard接入
现代项目线Java 17+、Boot 3.x、匹配的Spring Cloud AlibabaSpring 6/Jakarta生态、现代BOM、Micrometer/Tracing、容器化部署
组件独立线非Spring应用或SDK直接使用 sentinel-coreSphU.entry() 和 RuleManager

Sentinel 的资源、SlotChain、滑动窗口和规则判断思想不会因为 Boot 版本改变,但自动配置类、配置属性、Feign集成、指标桥接和依赖版本会变化。现代项目要同时锁定:

text
Java版本 -> Spring Boot版本 -> Spring Cloud发布列车
          -> Spring Cloud Alibaba版本 -> Sentinel依赖版本

Java 17没有正式虚拟线程;虚拟线程在Java 21正式发布。即使使用虚拟线程,数据库连接、HTTP连接、CPU和下游配额仍然有限,Sentinel的并发控制、限流和熔断仍然有价值。

三、控制面和数据面

mermaid
flowchart TD
    A["规则发布"] --> B["Dashboard"]
    B --> C["配置中心"]
    C --> D["实例订阅"]
    D --> E["本地RuleManager"]
    E --> F["请求本地判断"]
  • 控制面负责规则编辑、持久化、发布、审计和回滚。
  • 数据面位于每个应用进程内,正常请求不应每次远程调用 Dashboard。
  • Dashboard不可用时,本地已有规则通常仍会工作,但查看实时资源、临时推送或修改规则会受影响。
  • 配置中心不可用时,应用能否启动、是否沿用最后规则、恢复后怎样重订阅,取决于数据源和应用配置。
  • 各实例分别接收规则,因此“配置中心写入成功”不等于所有实例同一毫秒生效。

四、核心对象不是同一个东西

对象主要职责生命周期或范围常见误解
SphU给业务代码提供静态入口API进程级门面它自己保存全部统计
CtSph创建Entry、查找SlotChain、触发检查默认核心实现只是一层注解代理
ResourceWrapper封装资源名、EntryType、资源类型每次进入时引用,按资源缓存链资源名可以无限动态生成
Context表示一次调用入口和当前Entry栈通常绑定当前线程等于HTTP请求对象
Entry表示一次资源进入,记录开始、错误、Block和父子关系单次调用只负责计数,不需要exit
DefaultNode统计资源在某个Context调用路径下的数据Context与资源组合等于资源全局统计
ClusterNode聚合同一资源跨Context的总体统计进程内每个资源等于跨机器集群统计
OriginNode按调用来源细分统计资源与origin组合自动知道真实租户或调用方
ProcessorSlotChain串联节点构建、统计和规则检查通常按ResourceWrapper缓存每次请求重新创建整条链

这里的 ClusterNode 只是“当前JVM内同一资源的聚合节点”,不是多机器集群。多实例总配额要使用集群流控或在网关等统一入口治理,不能把 ClusterNode 名字误解为跨JVM共享计数。

资源名和Context名必须是低基数稳定标识,例如 inventory-query。若把订单号、用户ID、URL查询参数拼进资源名,会不断创建资源和SlotChain;达到内部链缓存上限后,新资源甚至可能不再经过正常规则检查,同时还会造成内存和指标基数膨胀。

五、Spring应用启动时发生什么

mermaid
flowchart TD
    A["应用启动"] --> B["Starter自动装配"]
    B --> C["注册调用适配器"]
    C --> D["创建规则数据源"]
    D --> E["加载并转换规则"]
    E --> F["RuleManager生效"]
    F --> G["启动心跳与命令服务"]
    G --> H["Dashboard发现实例"]

Sentinel核心通常按需初始化。Spring Cloud Alibaba可通过当前版本支持的 eager 配置提前建立通信和初始化基础组件,但“客户端已出现在Dashboard机器列表”不代表所有资源已经出现;资源通常要真正进入并产生统计后才能展示。

启动失败要先分层:

  1. Starter根本没生效:查BOM、条件装配和类路径。
  2. 应用启动了但Dashboard无机器:查Dashboard地址、心跳、容器网络和客户端IP。
  3. 机器在线但看不到资源:查资源是否真正被调用、注解代理是否生效、Web适配器是否注册。
  4. 资源存在但规则为空:查数据源订阅、DataId/Group/Namespace、JSON转换和RuleManager日志。

六、一次 SphU.entry() 的完整执行链

mermaid
flowchart TD
    A["SphU.entry"] --> B["取得Context"]
    B --> C["取得SlotChain"]
    C --> D["创建并压入Entry"]
    D --> E["逐Slot检查"]
    E --> F["执行业务或阻断"]
    F --> G["Entry.exit"]
    G --> H["完成退出统计"]

关键源码主线可以概括为:

text
SphU.entry
  -> CtSph.entryWithPriority
  -> ContextUtil.getContext
  -> lookProcessChain
  -> new CtEntry
  -> ProcessorSlotChain.entry
  -> 业务代码
  -> Entry.exit
  -> ProcessorSlotChain.exit

entry()exit() 必须成对。漏掉 exit() 不只是少一行清理代码,它会导致当前线程Entry栈、并发线程数、RT和完成统计不正确。手工API必须在 finally 中退出;异步API要在真正完成的回调中退出。

七、Context、Entry和节点树怎样配合

假设同一个 loadProduct 方法分别从开放API和管理后台进入:

mermaid
flowchart TD
    A["不同Context各建路径节点"] --> B["共同关联资源ClusterNode"]
  • NodeSelectorSlot 按Context名维护当前资源对应的 DefaultNode,所以链路流控能区分不同入口。
  • ClusterBuilderSlot 为同一资源关联进程级 ClusterNode,并可按origin建立来源统计。
  • Context 内部维护当前Entry和父子关系,嵌套资源会形成调用树。
  • ContextUtil.enter(name, origin) 中的origin必须由可信方式解析;不能直接相信外部请求随便传入的Header。

如果所有请求都落到默认Context,链路流控就无法准确区分入口。如果Context名使用动态用户ID,又会产生高基数节点。正确做法是使用稳定入口名,例如 public-apiadmin-apischeduled-task

八、SlotChain真实职责和顺序

Sentinel 1.8.x 的默认链由 DefaultSlotChainBuilder 通过SPI加载并按order排序。典型核心顺序如下;热点参数等扩展模块可插入额外Slot,未来版本也可能演进,因此不能把列表背成永远不变的协议。

顺序Slot真正做什么
1NodeSelectorSlot按Context构建调用路径节点
2ClusterBuilderSlot关联进程内资源聚合节点和origin节点
3LogSlot记录规则阻断等日志
4StatisticSlot包裹后续Slot,分别记录通过、阻断和退出统计
5AuthoritySlot按调用来源执行黑白名单判断
6SystemSlot依据入口QPS、线程、RT、Load、CPU等保护系统
7FlowSlot执行直接、关联、链路、本地或集群流控
8CircuitBreaker相关Slot检查熔断状态,并在请求结束后更新熔断器

StatisticSlot 为什么排在规则Slot之前,却只统计“通过请求”?因为它先调用后面的 fireEntry()

mermaid
flowchart TD
    A["进入StatisticSlot"] --> B["先执行后续检查"]
    B --> C["分别记录block或pass"]
    C --> D["业务完成后exit"]
    D --> E["记录RT、异常和线程数"]

这是一种“洋葱式”调用。若只按类的排列顺序理解,很容易误以为还没检查规则就已经把请求算作通过。

九、LeapArray滑动窗口内部结构

Sentinel不会为每个毫秒创建对象。LeapArray<T> 把统计周期均分为固定数量小窗口,并使用 AtomicReferenceArray<WindowWrap<T>> 循环复用桶。

设:

text
统计周期 intervalInMs = 1000ms
桶数量 sampleCount = 2
单桶长度 windowLengthInMs = 1000 / 2 = 500ms

窗口下标和开始时间近似为:

text
timeId = currentTimeMillis / windowLengthInMs
index = timeId % sampleCount
windowStart = currentTimeMillis - currentTimeMillis % windowLengthInMs
mermaid
flowchart TD
    A["请求到达并取得当前时间"] --> B["计算环形数组index"]
    B --> C["读取对应WindowWrap"]
    C --> D["按状态选择复用、CAS创建或加锁重置"]
    D --> E["在当前有效桶增加统计"]

每个 MetricBucket 会维护通过、阻断、异常、成功、响应时间等计数器。查询最近一段时间指标时,只汇总仍在统计周期内的有效桶。空间复杂度受固定桶数约束,不随运行时间无限增长。

9.1 为什么比固定窗口平滑

固定窗口若在每秒整点清零,00:00:00.90000:00:01.100 的200ms内可能分别吃满两个窗口阈值。滑动窗口通过多个小桶汇总最近区间,降低边界突刺,但桶越细,更新与汇总成本越高;桶越粗,统计越不平滑。

9.2 滑动统计不等于严格全局配额

单机窗口只看当前JVM。三个实例各配置100 QPS,在流量均匀时总通过量大约可到300 QPS;负载不均时某个实例可能先拒绝,而其他实例还有余量。若业务要求全局固定配额,应使用集群流控、网关统一限流或外部配额服务。

十、FlowSlot怎样选择节点和算法

一条FlowRule不只有 resource + count,还包括:

字段维度作用
resource匹配哪个资源
grade按QPS还是当前线程数判断
strategy直接、关联或链路控制
refResource关联资源或入口资源
limitApp按调用来源控制
controlBehavior快速失败、Warm Up、匀速排队等整形行为
clusterMode是否请求集群Token Server判断

10.1 QPS限流

直接快速失败模式会读取目标节点最近统计,在“已有通过量 + 本次申请量”超过阈值时拒绝。它适合执行时间短、主要风险是吞吐突增的接口。

阈值默认是单实例阈值。扩容会提高集群总配额,缩容会降低总配额,除非使用集群流控或发布系统动态重算每实例规则。

10.2 并发线程数限流

线程数模式读取当前资源尚未退出的通过请求数。它保护的是并发占用,不是线程池本身:

text
并发量 ≈ QPS × 平均响应时间秒数

若下游从50ms变成5s,即使QPS没变,并发也可能扩大100倍。并发规则能更直接地阻止慢调用继续占满Servlet线程或业务执行器,但它不能替代HTTP连接、读取超时。

10.3 关联和链路流控

  • 关联流控:当 refResource 压力达到阈值,限制当前资源,常用于让查询为核心写操作让路。
  • 链路流控:同一资源从不同Context入口进入时,只限制指定入口链路。
  • 两者依赖资源名、Context和调用树准确;Web适配器配置或Context丢失会让规则看起来“不生效”。

十一、Warm Up和匀速排队不是一回事

11.1 Warm Up

Warm Up用于系统刚启动、缓存未热、JIT尚未充分优化、连接池刚建立时逐步放量。其控制器维护类似令牌累积状态,通过 coldFactor、预热时长和目标阈值计算当前可通过速率,随后逐步接近稳定阈值。

它解决“冷系统瞬间吃满目标QPS”的问题,不解决下游已经故障的问题。熔断、客户端超时和容量隔离仍需单独配置。

11.2 匀速排队

Sentinel 1.8.x 的匀速控制器会根据目标速率计算相邻请求的期望通过时间,并维护 latestPassedTime。若预计等待时间不超过 maxQueueingTimeMs,当前调用线程会等待到计划时刻;超过最大等待时间则拒绝。

mermaid
flowchart TD
    A["请求申请"] --> B["计算期望通过时间"]
    B --> C["超过最大等待则拒绝"]
    C --> D["未超过则预占时间位置"]
    D --> E["当前线程等待后执行"]

它不是一个独立、持久、可恢复的消息队列。等待期间仍占用调用线程,进程崩溃后等待任务也会丢失。因此:

  • 短时、可等待的后台写入可以使用匀速排队。
  • 长时间削峰、必须不丢、需要重试的任务应使用Kafka、RocketMQ或RabbitMQ。
  • 用户请求总Deadline必须大于排队等待加真实执行时间,否则调用方早已超时而服务端仍在执行。

十二、热点参数限流内部边界

热点参数规则通过额外的参数流控Slot读取 entry() 传入的参数,根据参数索引取得值,再为不同参数值维护独立统计和Token状态。

text
总QPS 500并不高
其中 sku=A 的QPS 450
其余所有sku合计QPS 50

只做资源总限流无法保护 sku=A 对应的缓存分片或数据库行。热点规则可以为普通值设置默认阈值,并为特殊爆款设置例外阈值。

生产注意:

  1. @SentinelResource 必须把参数正确传入Sentinel Entry,参数索引从0开始。
  2. 参数类型支持范围受版本和适配器约束,常见基础类型和String最稳妥。
  3. 用户ID、搜索词等高基数参数会扩大统计缓存,需评估容量和淘汰行为。
  4. 热点限流只保护进入该规则的调用,不会自动修复Redis热Key、数据库热点行或分片倾斜。
  5. 参数例外规则属于业务配置,也必须审计和回滚。

十三、熔断器状态机和统计闭环

mermaid
flowchart TD
    A["CLOSED放行并统计"] --> B["指标超阈值后转OPEN"]
    B --> C["OPEN期间快速失败"]
    C --> D["到期后CAS抢探测资格"]
    D --> E["HALF_OPEN仅放行探测调用"]
    E --> F["成功回CLOSED,失败回OPEN"]

核心状态用原子引用维护。OPEN到期并不意味着所有请求同时放行;通常只有一个请求通过CAS抢到HALF_OPEN探测资格,其余请求继续失败,避免恢复瞬间再次压垮下游。

13.1 三类熔断规则

类型统计口径适用风险
慢调用比例RT超过慢调用阈值的请求占比下游不报错但越来越慢
异常比例异常请求数/总请求数持续5xx、连接失败、业务异常
异常数统计窗口内异常绝对数量低QPS但错误数量有明确红线

最小请求数防止小样本误判;统计窗口决定观察范围;熔断时长决定多久后探测。三者必须结合真实QPS、P95/P99和下游恢复时间设置。

13.2 业务异常会自动计入吗

规则阻断产生的是 BlockException,它应计入block,不应当作下游业务异常。业务异常是否进入异常统计取决于适配器是否把Throwable记录到当前Entry:

  • @SentinelResource AOP会按配置处理异常并记录。
  • 手工 SphU.entry() 时要显式使用 Tracer.traceEntry 或给当前Entry记录错误。
  • exceptionsToIgnore 等配置会改变统计口径。
  • 在另一个线程抛出的异常若没有关联原AsyncEntry,不会自动算到原资源。

如果监控显示下游大量500而Sentinel异常比例始终为0,不能先怀疑熔断算法,应先检查异常有没有被正确Trace、是否已在业务层吞掉、Feign解码后变成了正常返回对象。

十四、系统自适应保护是什么

SystemRule关注整个应用入口,而不是某个业务资源。常见指标包括:

  • 系统Load。
  • CPU使用率。
  • 全局入口QPS。
  • 全局并发线程数。
  • 全局平均RT。

Load保护不是“load一高就无条件拒绝”的简单开关。经典实现还会结合系统当前入口线程和由 maxQps × minRt 估算的容量边界做类似BBR的判断,减少偶发Load抖动造成误伤。

系统规则是最后防线,不应代替资源级容量规划。整机CPU很低但数据库连接池已满时,系统CPU规则可能完全不触发;仍需对具体数据库、下游调用和慢接口配置资源级限流、Bulkhead和超时。

十五、动态规则怎样进入RuleManager

以FlowRule为例,数据源完成的链路是:

mermaid
flowchart TD
    A["配置中心内容变化"] --> B["DynamicDataSource收到原始文本"]
    B --> C["Converter反序列化为FlowRule列表"]
    C --> D["SentinelProperty通知PropertyListener"]
    D --> E["FlowRuleManager按resource构建索引"]
    E --> F["替换本地规则容器"]
    F --> G["后续请求读取新规则"]

FlowRuleManager.loadRules(newRules) 的语义是用新列表替换旧规则,不是增量追加。空列表、错误DataId或错误反序列化策略可能让保护规则消失。

单个规则管理器会在监听回调中先构建新索引再更新本地容器,但以下事情不是一个全局原子事务:

  • FlowRule、DegradeRule、ParamFlowRule和SystemRule分别更新。
  • 不同应用实例分别收到通知。
  • 配置中心、Dashboard和应用本地可能短暂显示不同版本。
  • 规则与代码发布也不是天然原子切换。

生产发布应给规则包增加版本、校验、审批、灰度和回滚。先在少量实例验证block率和业务错误码,再扩大发布;不要直接覆盖全量生产DataId。

十六、Dashboard怎样发现和控制客户端

mermaid
flowchart TD
    A["Sentinel客户端启动"] --> B["启动本地命令服务端口"]
    B --> C["周期性向Dashboard发送Heartbeat"]
    C --> D["Dashboard记录app、IP、端口和时间"]
    D --> E["用户在Dashboard查看资源或规则"]
    E --> F["Dashboard调用客户端命令端点"]
    F --> G["客户端返回实时指标或更新本地规则"]

这解释了几个常见现象:

  • 应用到Dashboard是心跳方向,但Dashboard还必须能回连应用客户端端口。
  • 在Docker/Kubernetes中上报错误IP,会出现“机器在线但控制台拉不到数据”。
  • 多副本环境不能让所有实例抢占相同客户端端口映射。
  • Dashboard的机器列表有心跳时效,不是服务注册中心,也不负责业务流量转发。
  • 原生Dashboard直接推到客户端内存适合学习,不适合作为唯一生产规则源。

推荐的生产Push模式是让规则管理平台写入Nacos/Apollo等配置中心,再由所有客户端订阅。这样Dashboard或平台是编辑入口,配置中心才是可恢复的规则事实来源。

十七、本地流控与集群流控

17.1 本地流控

每个实例用自己的滑动窗口判断,优点是无网络依赖、延迟低、Token Server故障不影响;缺点是无法严格控制多实例总配额,并受负载不均影响。

17.2 集群流控

集群流控把全局Token判断集中到Token Server。Token Client收到业务请求后向Server申请Token,Server按namespace、resource和ruleId维护全局统计并返回通过或拒绝。

mermaid
flowchart TD
    A["任一实例收到请求"] --> B["Token Client申请Token"]
    B --> C["Token Server判断全局配额"]
    C --> D["有余量则通过,无余量则拒绝"]

Token Server可独立部署,也可由某个应用实例承担,具体能力看版本。引入后新增了网络跳数和控制服务故障域,必须明确:

  1. Token Server怎样发现、扩容和高可用。
  2. Client请求超时是多少。
  3. Server不可达时是回退本地判断、直接拒绝还是放行;策略必须显式验证,不能靠默认值猜测。
  4. 集群规则怎样持久化,ruleId和namespace是否稳定。
  5. Token Server本身的CPU、网络、RT和拒绝率怎样监控。

对于支付、开放平台租户配额等严格全局额度,集群流控更合适;对普通接口防雪崩,本地流控通常更稳健。它仍不是计费账本,不能替代数据库中的强一致额度扣减。

十八、注解、Web和Feign适配器做了什么

18.1 @SentinelResource

SentinelResourceAspect 拦截代理方法,在方法前创建Entry,捕获 BlockException 后查找blockHandler,捕获符合条件的业务异常后查找fallback,最终退出Entry。

常见失效原因:

  • 同类内部 this.method() 自调用绕过Spring代理。
  • 方法不是代理可拦截的调用路径。
  • blockHandler参数、返回类型或最后的 BlockException 不匹配。
  • fallback类不是静态方法或类声明方式不符合当前注解约定。
  • 业务先catch并返回成功对象,Sentinel看不到异常。

18.2 Web入口

Web适配器通常在过滤器层把URL或路由模板转换为资源,进入 EntryType.IN,并由统一 BlockExceptionHandler 返回429或业务限流错误码。不要把原始、含订单号的URL直接作为资源名,否则产生高基数资源。

18.3 OpenFeign

mermaid
flowchart TD
    A["业务调用Feign接口"] --> B["Feign动态代理"]
    B --> C["Sentinel适配器进入资源"]
    C --> D["规则阻断则进入FallbackFactory"]
    D --> E["通过则由LoadBalancer选择实例"]
    E --> F["HTTP客户端执行真实调用"]
    F --> G["记录RT、异常和熔断统计"]

Sentinel只决定是否允许尝试和怎样记录结果;连接超时、读取超时仍由HTTP客户端负责。Feign、Gateway、客户端和业务层多层重试会放大物理请求数,熔断统计看到的可能是每次Attempt,也可能是最终结果,取决于装饰顺序和适配器位置,必须用Trace和指标证明。

写操作Fallback不能返回伪成功。支付、库存、发货超时后真实结果可能已成功,应返回 UNKNOWN/PENDING_CONFIRMATION 并通过幂等键查询事实或补偿。

十九、异步和响应式调用为什么容易统计错

普通Context主要依赖线程本地状态。提交到线程池、CompletableFuture、Reactor切线程后,原线程的Context不会凭空传播。

Sentinel提供 SphU.asyncEntry()AsyncEntry:创建后会把异步Entry从当前线程栈移除,业务在异步完成回调中记录错误并调用 exit()。这样RT覆盖真正异步耗时,而不是只统计“任务提交用了1ms”。

mermaid
flowchart TD
    A["创建AsyncEntry"] --> B["提交异步任务"]
    B --> C["工作线程执行调用"]
    C --> D["完成回调记录结果"]
    D --> E["AsyncEntry.exit"]

错误做法是在请求线程中创建普通Entry,任务刚提交就exit。这样并发数和RT严重偏小,慢调用熔断无法反映真实下游耗时。Reactor项目应使用与当前版本匹配的适配器和上下文机制,不要手工把ThreadLocal对象跨线程复用。

二十、Java 17+与Spring Boot 3.x接入示例

依赖版本由匹配当前Boot 3.x的Spring Cloud Alibaba BOM统一管理:

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 使用Nacos持久化Sentinel规则时还需要匹配版本的数据源适配器 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

示例配置的属性名适用于常见Starter结构,落地时必须用当前依赖版本的配置元数据核对:

yaml
spring:
  cloud:
    sentinel:
      eager: true
      transport:
        dashboard: sentinel-dashboard.monitoring.svc:8858
        port: 8719
      datasource:
        flow:
          nacos:
            server-addr: nacos.infrastructure.svc:8848
            namespace: production
            group-id: SENTINEL_GROUP
            data-id: order-service-flow-rules
            data-type: json
            rule-type: flow

Java 17示例明确用 record 表示响应状态:

java
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class InventoryController {

    @GetMapping("/inventories/{sku}")
    @SentinelResource(
            value = "inventory-query",
            blockHandler = "blocked",
            fallback = "failed")
    public InventoryResult inventory(@PathVariable String sku) {
        if (sku.startsWith("FAIL")) {
            throw new IllegalStateException("remote inventory unavailable");
        }
        return new InventoryResult(sku, 12, "CONFIRMED", null);
    }

    public InventoryResult blocked(String sku, BlockException cause) {
        return new InventoryResult(sku, null, "RATE_LIMITED",
                cause.getClass().getSimpleName());
    }

    public InventoryResult failed(String sku, Throwable cause) {
        return new InventoryResult(sku, null, "UNKNOWN",
                cause.getClass().getSimpleName());
    }

    public record InventoryResult(
            String sku, Integer quantity, String status, String reason) {
    }
}

RATE_LIMITED 表示调用根本没进入业务,调用方可按Retry-After和幂等性决定是否重试;UNKNOWN 表示业务调用失败或结果不确定,不能等价成“库存为0”。这段代码使用Java 17的record,不能复制到JDK 8项目。

二十一、JDK 8可运行Demo:直接观察通过和阻断

依赖示例:

xml
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.8</version>
</dependency>
java
import java.util.Collections;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
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;

public final class SentinelCoreDemo {
    public static void main(String[] args) {
        FlowRule rule = new FlowRule();
        rule.setResource("inventory-query");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(2.0d);
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        FlowRuleManager.loadRules(Collections.singletonList(rule));

        int passed = 0;
        int blocked = 0;
        for (int i = 0; i < 6; i++) {
            Entry entry = null;
            try {
                entry = SphU.entry("inventory-query");
                passed++;
            } catch (BlockException ex) {
                blocked++;
            } finally {
                if (entry != null) {
                    entry.exit();
                }
            }
        }
        System.out.println("passed=" + passed);
        System.out.println("blocked=" + blocked);
    }
}

在同一统计时段连续请求6次、QPS阈值为2时,典型结果是前2次通过、后4次被阻断。这个Demo用于观察Core主链;生产阈值、并发和时间分布必须通过压测验证。

二十二、商业场景:订单提交、库存与营销查询

订单系统有三类完全不同的保护目标:

资源主要风险建议治理
submit-order核心写链路,重复提交和数据库容量网关租户限流、服务本地并发保护、幂等键,不做伪成功Fallback
lock-inventory下游变慢导致上游线程堆积HTTP超时、慢调用熔断、Bulkhead、结果未知查询
promotion-query大促突发流量但可降级Warm Up、QPS限流、缓存和空优惠降级标记
product-detail少量爆款SKU形成热点热点参数限流、Redis热Key治理、本地缓存

容量设计示例:单实例压测在P99小于200ms时稳定承受300 QPS,部署4实例,安全系数0.7:

text
估算总安全吞吐 = 300 × 4 × 0.7 = 840 QPS

若使用本地限流,可先按每实例约210 QPS配置,但必须考虑负载不均和滚动发布时实例数变化;若业务合同规定租户全局最多800 QPS,应在网关或集群流控做全局配额,数据库仍保留最终业务约束。

订单提交总Deadline为1500ms时,不能让Sentinel排队1000ms后再由Feign读取超时1000ms,更不能外层和内层各重试3次。预算要从入口向下游递减,并记录每个逻辑请求对应的物理Attempt数。

二十三、必须认识的失败窗口

失败窗口表面现象真正风险恢复原则
Dashboard不可达看不到机器或规则临时运维入口失效,本地旧规则仍可能运行查心跳与回连,不要先重启全部应用
配置中心不可达新规则不下发实例规则版本分裂保留最后可用规则、告警版本差、恢复后校验收敛
错误规则发布为空列表block突然归零保护能力整体消失版本化规则、非空校验、快速回滚
阈值误改过小大量FlowException正常流量被误伤灰度规则、按实例看block率、回滚版本
Token Server不可达集群规则超时或行为变化全局配额失控或全拒绝预先验证failover策略并演练
异常被业务吞掉下游500但不熔断Sentinel统计口径失真检查Tracer、Fallback和统一返回体
匀速排队过长服务端线程等待调用方超时后服务端仍执行统一Deadline,长削峰改MQ
扩容后本地阈值未重算总通过量上升下游被扩容流量打垮集群配额或自动按容量计算规则
动态资源名爆炸内存和指标上涨新资源链无法正常创建使用模板化低基数资源名
异步提前exitRT很低但下游很慢熔断永远看不到真实慢调用使用AsyncEntry并在完成回调退出

二十四、线上大量Block或熔断Runbook

24.1 先确定谁拒绝了请求

  1. 从统一错误码、异常类和日志区分 FlowExceptionDegradeExceptionParamFlowExceptionSystemBlockExceptionAuthorityException
  2. 记录应用、实例、资源名、规则版本、ruleId、origin、入口Context和traceId。
  3. 确认请求有没有真正到业务方法、Feign和下游,避免把Block当成下游500。

24.2 再确定为什么命中

mermaid
flowchart TD
    A["大量请求被Block"] --> B["先识别具体异常类型"]
    B --> C["再检查该规则对应指标"]
    C --> D["对照实例、规则版本和发布时间"]
    D --> E["判断误发布还是真实过载"]

24.3 必须收集的证据

  • 最近15分钟QPS、pass QPS、block QPS和线程数。
  • RT平均值、P95、P99、慢调用比例、异常比例。
  • 应用实例数、各实例流量分布、发布和扩缩容事件。
  • Servlet/Netty线程、业务线程池、HTTP连接池、数据库连接池。
  • Feign/Gateway/客户端重试次数和物理Attempt。
  • Dashboard机器心跳、客户端命令端口、规则源版本与实例本地规则。
  • 下游慢SQL、GC暂停、CPU节流、网络错误和依赖限流。

24.4 恢复顺序

  1. 若是错误规则发布,回滚到已验证规则版本,不要手工逐实例修改内存。
  2. 若是真实流量超过容量,先保护核心链路、关闭非核心入口、停止异常重试源。
  3. 若是下游变慢,限制并发和重试,保持熔断,修复下游瓶颈后小流量探测。
  4. 若需要扩容,确认下游数据库和配额也能承受,并处理本地阈值随实例数放大的问题。
  5. 恢复后验证所有实例规则版本一致、block率回落、业务成功率和下游容量稳定。

二十五、监控不能只看Block QPS

指标或证据要回答的问题
pass/block QPS规则是否在工作,拒绝比例是多少
当前线程数慢调用是否导致并发占用
RT与慢调用比例是否接近熔断阈值
异常数与异常比例业务错误是否被正确记录
CircuitBreaker状态哪个资源何时OPEN/HALF_OPEN/CLOSED
规则版本和更新时间是否刚发布错误规则
客户端心跳和命令端口Dashboard是否能发现并回连客户端
Token Server RT/错误集群流控是否成为新瓶颈
Fallback调用量是否把真实失败隐藏成表面成功

指标要按应用、实例、资源、结果类型和规则版本拆分,但标签不能使用订单号、用户ID等高基数值。日志中也不要输出Token、身份证、手机号等敏感数据。

二十六、常见错误与后果

错误做法为什么错后果
把Sentinel当线程池它主要做统计和准入判断接受的慢请求仍可能耗尽连接池
所有资源都配置同一个QPS不同接口成本不同核心接口被误伤或昂贵接口无保护
blockHandler返回正常成功码规则阻断被伪装为成功上游停止补偿,业务事实错误
只在Dashboard内存改规则重启后不可恢复保护能力漂移或丢失
用订单号作为资源名资源和SlotChain高基数内存上涨、监控不可用、规则失配
匀速排队代替MQ调用线程等待且不持久超时、重启丢失和线程耗尽
只看平均RT长尾被平均值掩盖P99恶化后才发生雪崩
熔断后无限重试OPEN本应快速失败重试风暴和恢复探测被淹没
扩容时保持“集群总阈值”配置在每实例每台都按总量放行下游流量随实例数成倍放大
手工Entry不在finally退出统计生命周期不闭合线程数、RT和调用树失真

二十七、关联知识点

真正理解Sentinel的标准不是“会在Dashboard点一条规则”,而是能从一次Entry的生命周期解释统计从哪里来、规则在哪个Slot判断、控制面怎样把版本化规则送到每个实例,以及规则、网络和下游同时异常时怎样保住核心业务并恢复。