ElIauk

Sentinel 限流降级实战

限流降级不是加个注解就完事 Sentinel 在 Spring Cloud Alibaba 体系里承担的是“入口保护”:按 QPS 或并发线程数限流、按慢调用比例或异常比例熔断、对热点参数单独限流。它的接入成本很低,一个 starter 加一个 dashboard 地址就能跑;但真正上线后,大多数人

评论
Sentinel 限流降级实战

限流降级不是加个注解就完事

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 修改后又被下一次拉取覆盖,看起来像“规则自己变了”。持久化方案确定后,就固定在一个地方改。

排查步骤

  1. 暴露 /actuator/sentinel 端点,确认 rules 里规则已经加载,资源名和线上一致。
  2. 看 dashboard 的实时监控,确认请求确实经过了 Sentinel;没有数据说明资源没被拦截,先查资源名和注解。
  3. 打开 csp.sentinel.log.dir 下的日志,看规则拉取、推送、连接 dashboard 是否报错。
  4. 用 curl 或压测工具打到阈值以上,确认返回的是限流响应而不是业务异常。
  5. 重启一次应用,确认规则还在;如果不在,说明持久化没配好。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者