故障发生在一次会员数据导出的时候。
那天上午有家商会在批量导出全量会员名单,逻辑是分页查会员接口,每页两百条,循环拉取。与此同时其他用户在前台正常浏览。两边请求叠在一起,会员查询接口响应时间从几十毫秒涨到几秒,前台大面积超时。
事后复盘,问题不在导出这个需求本身,而是没给不同来源的流量设边界。这篇把后来补上的防护记录一下。
先说分层思路。限流熔断不是一个开关,是三层:入口层挡总量,应用层防雪崩,热点层防单点,各管各的事。
第一层是 API 网关,按来源和 QPS 限流。导出走单独路由和单独配额,跟前台浏览分开,即使导出把配额占满也不影响前台。这一层的优势是在流量进应用之前就拦住,不消耗应用线程。
第二层是应用侧的 Sentinel,做熔断降级。限流解决"量太大",熔断解决"下游变慢",两者不一样:限流是主动拒绝,熔断是依赖出问题时快速失败,避免线程池被拖死。我们给会员查询配的是慢调用比例熔断,一分钟内慢调用占比过阈值就打开断路器,后续请求直接走降级逻辑。
第三层是热点参数限流。某个商会 ID 突然被大量查询(比如办活动时所有人刷同一页面),这种单点击穿用全局限流拦不住,因为总 QPS 并不高。Sentinel 的热点参数规则可以针对参数值单独限流。
核心配置如下。
@PostConstruct
public void initRules() {
// 熔断:慢调用比例过半,熔断 30 秒
DegradeRule degrade = new DegradeRule("member:query")
.setGrade(RuleConstant.DEGRADE_GRADE_RT)
.setCount(500) // 超过 500ms 算慢调用
.setSlowRatioThreshold(0.5)
.setStatIntervalMs(60_000)
.setMinRequestAmount(20)
.setTimeWindow(30);
DegradeRuleManager.loadRules(List.of(degrade));
// 热点参数:单个 orgId 每秒 50 次
ParamFlowRule param = new ParamFlowRule("member:query")
.setParamIdx(0)
.setCount(50);
ParamFlowRuleManager.loadRules(List.of(param));
}
资源名要在调用处埋点,用注解很简洁:SentinelResource 的 value 填 member:query,blockHandler 处理被限流,fallback 处理业务异常。这两个要分开写,混在一起会导致限流和真故障返回同样的结果,排查时分不清。
接下来是几个实操细节。
一是规则持久化。Sentinel 默认把规则放内存,应用重启规则就没了,生产环境必须配持久化,常见做法是规则写进 Nacos,应用启动时拉取。推模式下控制台改动实时生效,但要和应用连同一个 Nacos;拉模式定时去拉,改动到生效有延迟。规模不大时拉模式够用。
二是熔断后的半开恢复。断路器打开后,Sentinel 会在时间窗口结束时放一个请求试探,成功就关闭,失败继续打开。这是内置机制,但时间窗口别设太短,设十秒的话下游还没恢复就又放流量进去,会反复横跳。我们设的三十秒。
三是降级返回什么。限流和熔断之后不能返回 500,客户端会当成服务故障。我们的做法是返回特定状态码,429 和 503 分开,响应体里带提示和预计恢复时间。前端拿到 429 提示稍等,拿到 503 引导到静态缓存页。
四是阈值怎么定。别拍脑袋,先在压测环境测出接口容量,比如单实例能扛三百 QPS 而响应时间不超过二百毫秒,生产按七折设置,留余量,上线后再按监控调整。
五是监控。光配规则不看数据等于没配。我们在 SLS 里盯三个指标:限流拒绝次数、熔断打开次数、慢调用比例。熔断打开次数是关键,这个数经常性大于零说明下游有问题,限流只是盖住了症状,根因还得查。
顺带说导出本身。加了限流之后,我们把导出改成了异步:用户点导出,任务进队列,完成后生成文件通知下载,不再实时翻页调查询接口。限流是防守,异步导出才是治本,两件事一起做才算解决。
还有个容易忽略的点:网关限流和应用限流的配额要对齐。网关放进来两百 QPS,应用侧只配了一百五,那五十的差值会在应用层被拒,用户看到的是应用层错误提示而不是网关的,反而更迷惑。建议应用侧阈值略高于网关,让网关成为主要拦截点。
我们这边落地的商会管理系统叫未来漫城·商会互联平台,会员查询、商机匹配、活动报名几个高频接口都配了三层防护,规则统一放 Nacos 管理。
最后一句经验:限流熔断是最后一道防线,不是性能优化手段。接口慢了,加限流只是让它慢的时候不拖垮全局,真正该做的还是把查询本身优化好,别把这两件事搞反。