单体不是免死金牌:一次慢查询能拖垮整个 JVM
单体不是免死金牌:一次慢查询能拖垮整个 JVM
很多人觉得限流熔断是微服务才需要的东西,单体应用就一个进程,加了也没用。这个判断在流量平稳时看不出问题,但只要有一条链路变慢,整个进程都会被拖下水。我们线上有个订单服务,单体部署,Tomcat 默认 200 个工作线程。某次报表 SQL 漏了索引,单次查询 8 秒,运营后台的导出请求把 200 个线程全占满,前台下单接口跟着 502——它和报表毫无关系,只是共用了同一个线程池。
这就是单体的风险:故障域是共享的。线程池、数据库连接池、堆内存、CPU,任何一项被耗尽,所有接口一起挂。限流熔断在单体里的价值不是防外部攻击,而是把故障关进笼子里。
先做舱壁:把线程池和连接池按依赖切开
限流之前先隔离,否则会限错对象。具体三件事:
- 给每个下游依赖(支付网关、短信、报表库、缓存)配独立的
ThreadPoolBulkhead或信号量,比如短信 20 个并发、支付 50 个并发,超了直接拒绝而不是排队; - 数据库按业务拆连接池,报表走单独的只读池,
maxPoolSize=8写死,不和主库共用 HikariCP; - 所有跨进程调用强制设超时,HTTP 客户端
connectTimeout=500ms、readTimeout=2s,不允许是 0 或无限。
隔离做完,报表把只读池打满时,下单接口只是报表功能不可用,主流程照常。
限流:QPS 从压测数据来,不从感觉来
入口限流用拦截器加令牌桶,按接口维度配阈值。阈值必须来自压测:单机 P99 开始翘头时的 QPS,取 70% 作为线上阈值。我们下单接口压到 1200 QPS 时 P99 明显变差,阈值就设 800。
被限流的请求返回 429 并带 Retry-After,不要静默丢弃,否则上游会一直重试放大流量。关键依赖也加客户端限流,比如调用风控接口限制 300 QPS,超出走本地默认策略,比干等 2 秒超时体验好得多。
熔断:慢调用比例比异常率更早发现问题
熔断器只看异常率会漏掉慢调用——下游没报错,只是 RT 从 50ms 变成 3s,异常率还是 0。配置上要同时开两个条件:
- 慢调用比例:统计窗口 10s,RT 超过 1s 记为慢调用,比例 > 50% 且请求数 ≥ 20 时熔断;
- 异常比例:窗口内异常率 > 50% 且请求数 ≥ 20 时熔断;
- 熔断 10s 后进入半开,放 5 个探针请求,成功再恢复。
请求数下限很重要:低峰期 2 个请求挂 1 个就是 50%,不加下限会频繁误熔断。
落地顺序和几个真实的坑
顺序建议是:超时 → 隔离 → 限流 → 熔断 → 降级。降级逻辑最后写,因为得先知道降级成什么业务语义。
- 降级返回值要能被业务识别,比如库存查询降级返回
unknown而不是0,否则"查不到"会被当成"没库存",直接少卖; - 阈值放配置中心,但必须加变更审计,我们有过一次手滑把限流阈值改成 10,五分钟内全站 429;
- 规则上线前用影子流量或压测环境验证,别直接在生产上调参数;
- 把熔断状态、限流计数打进 Micrometer 指标并配告警,否则熔断了没人知道,等于没做。
- 点赞
- 收藏
- 关注作者
评论(0)