单体服务不是没有"下游"

举报
yd_235578340 发表于 2026/10/29 10:25:47 2026/10/29
【摘要】 单体服务不是没有"下游" 很多团队觉得限流熔断是微服务才需要的东西,单体应用里"函数直接调用,哪来的网络故障"。但一个真实请求往往串行依赖 MySQL、Redis、第三方支付、短信网关,还和所有其他接口共享同一个 Tom

单体服务不是没有"下游"

很多团队觉得限流熔断是微服务才需要的东西,单体应用里"函数直接调用,哪来的网络故障"。但一个真实请求往往串行依赖 MySQL、Redis、第三方支付、短信网关,还和所有其他接口共享同一个 Tomcat 线程池。任何一个慢依赖都能把 200 个工作线程吃干净,然后整个应用 502,包括那些本来完全健康的接口。这就是单体最典型的故障放大:没有隔离,故障就是全站级的。

入口限流:先保住线程池

第一道闸门放在 HTTP 入口。先确认 server.tomcat.threads.max(常见 200)和 server.tomcat.accept-count(100),这两个数字决定了你的服务最多能"卡住"多少请求。然后用 Resilience4j 的 RateLimiter 或 Sentinel 的 FlowRule 做单机限流,粒度按"接口 + 调用方"划分,比如 /api/export 对每个租户限 5 QPS,而不是只做全局限流——全局限流会让一个刷接口的租户挤掉所有人。

被限流时返回 429 并带上 Retry-After,不要图省事返回 500,否则上游会重试,把限流变成雪崩。单机限流优先用 Bucket4j 这类本地令牌桶,别为了"精确"上 Redis 集中限流,那样限流器自己就成了新的故障点。

熔断要挂在"依赖边界"上

熔断器的划分维度是依赖,不是接口。paymentClient、smsClient、userCache 各一个,这样支付挂了只影响支付,不会把读缓存也一起断掉。典型配置:slidingWindowSize=20、minimumNumberOfCalls=10、failureRateThreshold=50、waitDurationInOpenState=30s、permittedNumberOfCallsInHalfOpenState=3。

比异常率更值得开的是慢调用熔断:slowCallDurationThreshold=2s。线上大部分故障不是抛异常,而是"变慢",异常率熔断往往等线程池已经打满才触发。第三方调用建议配独立线程池隔离(core=8, max=16, queue=32),队列满了直接拒绝,绝不要用无界队列——无界队列等于没有熔断。

几个真实踩过的坑

  • 熔断器 key 用完整 URL 动态创建,带了 query 参数,导致熔断器实例数量爆炸、内存缓慢泄漏。key 必须收敛到有限集合。
  • 半开状态放行太多请求,恢复瞬间把刚缓过来的下游再次打挂。半开只放 1~3 个探测请求。
  • 阈值按日均 QPS 定,没看峰值,大促时把正常用户限掉了。阈值应取历史 P99 再留 30% 余量。
  • 降级返回 null 而不是空对象,上游 NPE,故障从"慢"升级成"错"。
  • 熔断打开没有告警,靠用户投诉才发现。打开时必须打点 + ERROR 日志。

落地顺序:先超时,再熔断,最后限流

收益排序很清楚:第一步给所有外部调用加超时(连接 1s、读 3s),这一步几乎不写代码却能解决大半问题;第二步按依赖加熔断;第三步才是入口限流。灰度上先跑 count 模式,只统计不拦截,观察一周确认阈值合理后再开拦截,否则第一次上线就可能误伤正常流量。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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