限流降级不是加个注解就完事
Sentinel 在 Spring Cloud Alibaba 体系里承担的是“入口保护”:按 QPS 或并发线程数限流、按慢调用比例或异常比例熔断、对热点参数单独限流。它的接入成本很低,一个 starter 加一个 dashboard 地址就能跑;但真正上线后,大多数人会遇到两个问题:规则重启就没了,以及规则明明配了却不生效。
接入:dashboard 只是控制台,不是规则仓库
依赖是 spring-cloud-starter-alibaba-sentinel,配置里给 dashboard 地址和客户端通信端口:
spring:
cloud:
sentinel:
eager: true
transport:
dashboard: 127.0.0.1:8080
port: 8719
datasource:
flow:
nacos:
server-addr: 127.0.0.1:8848
namespace: dev-id
group-id: SENTINEL_GROUP
data-id: order-service-flow-rules
data-type: json
rule-type: flow
eager: true 让客户端在启动时就建立与 dashboard 的连接,否则要等第一次请求经过 Sentinel 才初始化,这段时间规则是空的。transport.port 是客户端暴露给 dashboard 的端口,同一台机器上跑多个服务时必须各自不同,否则后启动的会连不上。
关键认知:开源版 dashboard 默认只把规则推送到客户端内存,不写回 Nacos。重启后规则丢失是正常行为。要持久化,有两条路:改造 dashboard 的推送逻辑,让它同时写 Nacos;或者像上面这样,把规则直接维护在 Nacos,由客户端通过 datasource 拉取,dashboard 仅用于观察和临时调试。后者更简单,也更适合把规则纳入 GitOps。
流控:QPS 和线程数怎么选
流控规则有两个核心维度:
| 维度 | grade | 适用场景 | 风险 |
|---|---|---|---|
| QPS | 1 | 接口处理快、耗时稳定 | 慢请求堆积时挡不住 |
| 线程数 | 0 | 下游慢、需要保护线程池 | 阈值难估,容易误杀 |
判断方法很直接:如果接口 P99 在几十毫秒、瓶颈是 CPU,用 QPS;如果接口会调用慢 downstream、瓶颈是 Tomcat 线程或连接池,用线程数。线程数阈值要明显小于容器线程池大小,比如 Tomcat 默认 200,阈值可以设在 50 到 80,留出余量给非核心接口。
快速失败之外,还有预热和排队两种控制行为。预热适合刚启动、缓存还没热、连接池还没建立的场景,让流量在 warmUpPeriodSec 内逐步爬升;排队适合削峰,但 maxQueueingTimeMs 设大了会把请求活活拖到超时,通常不超过接口超时的三分之一。
熔断降级:慢调用比例比平均 RT 好用
Sentinel 1.8 的熔断规则有三种:慢调用比例、异常比例、异常数。慢调用比例最实用,因为它能表达“超过 800ms 的请求占比超过 50% 就熔断”,比“平均 RT 超过多少”更抗抖动。
@SentinelResource(value = "queryOrder",
blockHandler = "queryBlocked",
fallback = "queryFallback",
exceptionsToIgnore = BizException.class)
public OrderVO queryOrder(Long orderId) {
return orderClient.getById(orderId);
}
public OrderVO queryBlocked(Long orderId, BlockException ex) {
log.warn("订单查询被限流, orderId={}, type={}", orderId, ex.getClass().getSimpleName());
throw new BizException("FLOW_LIMIT", "请求过于频繁,请稍后再试");
}
public OrderVO queryFallback(Long orderId, Throwable throwable) {
log.error("订单查询降级, orderId={}", orderId, throwable);
return OrderVO.unavailable(orderId);
}
blockHandler 只处理 BlockException,fallback 处理业务异常,两者签名都要和原方法参数一致,blockHandler 末尾多一个 BlockException,fallback 末尾多一个 Throwable。exceptionsToIgnore 很重要:如果不排除业务异常,参数校验失败、订单不存在这类正常错误也会走降级,问题被悄悄吞掉。
熔断打开后有一个 timeWindow 窗口,窗口结束进入半开状态,放一个请求试探。试探成功才关闭熔断,失败则继续打开。所以降级返回兜底数据时,要确保这次试探能真实反映下游是否恢复。
常见坑
资源名对不上。 Spring MVC 接口的默认资源名是 GET:/api/orders/{id} 这种带方法前缀的形式,不是纯路径。规则里写 /api/orders/{id} 永远不会命中。最稳妥的方式是给关键接口显式加 @SentinelResource,用自定义名字,避免受 URL 匹配规则影响。
注解自调用失效。 在同一个类里 this.queryOrder() 不经过 Spring 代理,Sentinel 切面不生效。要么把方法拆到另一个 Bean,要么通过代理调用。
方法签名不匹配。 blockHandler 少写 BlockException、参数顺序不对,运行时才报找不到方法,而且异常信息不明显。改完注解后一定要用一个压测请求实际触发一次。
客户端端口冲突。 同机部署多个服务时都用了默认 8719,后启动的连不上 dashboard,表现为“别的服务有实时数据,这个服务没有”。
Gateway 规则类型不同。 网关限流用的是 gw-flow/gw-api-group,还需要 spring-cloud-alibaba-sentinel-gateway 相关依赖,不能直接复用 Web 应用的流控规则。
Dashboard 临时规则被覆盖。 从 Nacos datasource 拉取的规则,会在 dashboard 修改后又被下一次拉取覆盖,看起来像“规则自己变了”。持久化方案确定后,就固定在一个地方改。
排查步骤
- 暴露
/actuator/sentinel端点,确认rules里规则已经加载,资源名和线上一致。 - 看 dashboard 的实时监控,确认请求确实经过了 Sentinel;没有数据说明资源没被拦截,先查资源名和注解。
- 打开
csp.sentinel.log.dir下的日志,看规则拉取、推送、连接 dashboard 是否报错。 - 用
curl或压测工具打到阈值以上,确认返回的是限流响应而不是业务异常。 - 重启一次应用,确认规则还在;如果不在,说明持久化没配好。
评论