单体服务的容量,不是“一个进程”的容量

举报
yd_257405204 发表于 2026/11/14 09:06:28 2026/11/14
【摘要】 单体服务的容量,不是“一个进程”的容量 很多团队认为限流熔断是微服务才需要的东西:单体应用所有调用都在进程内,不会有网络失败。但真实故障恰好相反:单体把所有接口塞进同一个 Tomcat 线程池(默认 maxThreads

单体服务的容量,不是“一个进程”的容量

很多团队认为限流熔断是微服务才需要的东西:单体应用所有调用都在进程内,不会有网络失败。但真实故障恰好相反:单体把所有接口塞进同一个 Tomcat 线程池(默认 maxThreads=200),只要一个慢接口把线程占满,整个进程的所有接口会一起超时,爆炸半径从“一个服务”变成“一个进程内的全部业务”。所以单体里限流的目标不是防外部攻击,而是防止某个接口独吞公共资源(线程、连接、内存)。

第一层:按接口分组做并发隔离

先做并发数隔离,不要一上来就做 QPS 限流,因为线程被打满才是进程级死亡的直接原因。在 Filter 里按 URI 前缀分组,每组一个 Semaphore:

  • 普通查询组 permits=60,下单组 permits=40,报表导出组 permits=8(导出最容易拖垮 JVM)
  • tryAcquire(10, TimeUnit.MILLISECONDS) 拿不到直接返回 429 并带 Retry-After: 1,不要排队等待
  • 释放必须放在 finally 中,并排除 Servlet 异步请求和 WebSocket 长连接,否则会提前归还令牌

初始阈值别拍脑袋:用压测得出该接口 P99 响应时间,按“目标 QPS × P99 响应时间 ÷ 1000 × 1.5”估算初始并发数,再观察一周调整。

第二层:给下游依赖装熔断器

单体里典型的下游是第三方 HTTP、Redis、DB。DB 靠连接池上限加超时兜底,通常不适合熔断;HTTP 下游则非常适合 Resilience4j 的 CircuitBreaker。关键在别用默认值:

  • slidingWindowSize=100 配 minimumNumberOfCalls=20,否则凌晨低谷时 3 次失败就跳闸
  • failureRateThreshold=50,同时配 slowCallRateThreshold=60 和 slowCallDurationThreshold=2s,慢调用也要计入失败
  • waitDurationInOpenState=30s,permittedNumberOfCallsInHalfOpenState=5,半开放行探测成功才闭合

粒度按“下游系统 + 接口分组”,不要整个服务只挂一个熔断器,否则一个边缘接口故障会把主流程一起断掉。

第三层:降级要有返回值,不是抛异常

熔断打开后最怕的是异常直接抛到前端,用户看到 500。给每个被保护调用准备兜底:商品详情读本地缓存、推荐位返回空列表、积分查询返回“稍后重试”。降级分支必须打点,例如 counter("fallback.total", "reason", reason),否则你永远不知道降级实际跑了多久、影响了多少人。同理,429 也要带上明确的错误码,让前端能区分“限流”和“真故障”。

落地顺序与验证

  • 先接监控再开限流:用 Micrometer 暴露熔断状态、拒绝计数、信号量等待时长,接入 Prometheus 看板
  • 阈值放配置中心并支持热更新,出问题时能在 30 秒内调回去,而不是重新发版
  • 用 tc qdisc add dev eth0 root netem delay 3000ms 或混沌工具注入延迟,验证熔断是否在 10 次调用内打开、恢复后是否自动闭合
  • 上线灰度时先“只记录不拦截”(shadow 模式),对照日志确认阈值合理再真正拒绝
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。