微服务核心04:熔断限流
把限流、熔断、隔离和降级放到同一条调用链里看,理解系统在过载和下游故障时如何快速止损。
熔断限流解决什么问题
熔断限流的底层判断很朴素:系统资源是有限的,不能让所有请求都无条件进入核心链路。请求太多时要限流,下游持续失败时要熔断,局部资源紧张时要隔离,非核心能力不可用时要降级。
它们不是为了”让接口看起来更高级”,而是为了在压力和故障出现时保住系统的基本盘。
更准确地说,这四个控制点解决的是不同问题:
| 机制 | 解决的问题 | 核心思路 |
|---|---|---|
| 限流 | 流量太多 | 不让过多请求进入系统 |
| 熔断 | 下游持续失败 | 不要继续调用明显不健康的下游 |
| 隔离 | 局部资源拖垮全局 | 不让一个资源池拖垮所有链路 |
| 降级 | 非核心能力不可用时保核心 | 能力不完整时仍然保住核心业务结果 |
场景:支付通道超时引发雪崩
订单服务调用支付服务,支付服务再调用第三方支付通道。平时一次支付确认 200ms,某天第三方通道开始超时,支付服务响应变成 5s。
如果订单服务没有超时、没有隔离、还配置了多次重试,就会发生连锁反应:
- 订单服务线程被支付调用占住
- 新的下单请求继续进来并排队
- 排队请求越来越多,连接池和线程池被打满
- 用户看到的是”整个下单不可用”,而不是”支付通道慢”
熔断限流要解决的就是这种扩散:下游已经不健康时,调用方不要继续把自己拖死。
限流算法怎么选
限流算法要看流量形态。没有哪个算法永远最好,要看场景。
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 固定窗口 | 按时间窗口计数 | 实现简单 | 窗口边界可能出现瞬时两倍流量 | 粗略限流、内部接口 |
| 滑动窗口 | 细粒度分片滑动统计 | 更平滑准确 | 统计成本更高 | 精确限流、入口网关 |
| 令牌桶 | 匀速生成令牌,请求取令牌 | 允许一定突发,保护入口 | 需要维护令牌状态 | 入口流量、API 网关 |
| 漏桶 | 请求匀速流出 | 削峰填谷,输出稳定 | 无法应对突发流量 | 消息处理、后台任务 |
项目里不需要一上来追求最复杂的算法,先问清楚要保护的是入口 QPS、接口并发,还是下游资源容量。
熔断器状态机
熔断更像一个状态机:
熔断器有三种状态:
- 关闭(Closed):正常状态,请求正常通过,统计失败率
- 打开(Open):失败率超过阈值,快速失败,不再调用下游
- 半开(Half-Open):休眠一段时间后,放行少量请求探测下游是否恢复
状态机的关键是统计窗口和恢复策略。窗口太短会被偶发抖动误伤,窗口太长又会让故障扩散太久;半开探测放量太多,会把刚恢复的下游再次打趴。
一个简化的调用策略可以这样表达:
payment:
call:
timeoutMs: 800
maxConcurrent: 40
retry: 0
circuitBreaker:
errorRate: 50
windowSeconds: 30
sleepSeconds: 10
fallback: async-confirm
这不是某个框架的标准格式,而是设计思路:先限制等待时间和并发,再根据失败比例熔断,最后选择业务能接受的兜底方案。
隔离的多种形式
隔离不只是线程池。它可以是很多种形式,只要能阻止局部故障抢占全局资源,它就在发挥作用。
| 隔离方式 | 说明 | 适用场景 |
|---|---|---|
| 线程池隔离 | 不同链路用不同线程池 | CPU 密集、下游调用 |
| 连接池隔离 | 不同服务用不同连接池 | 数据库、HTTP 连接 |
| 队列隔离 | 不同任务用不同队列 | 异步任务、消息处理 |
| 租户隔离 | 不同租户资源独立 | 多租户系统 |
| 机房隔离 | 不同机房独立部署 | 高可用部署 |
常见误区
误区一:限流只做在网关就够了
入口限流很重要,但内部服务也需要保护。比如某个内部任务突然批量调用订单服务,流量不经过外部网关,订单服务自己也要有并发和 QPS 保护。
误区二:熔断就是下游挂了才打开
熔断关注的是失败率、慢调用比例和资源占用,不一定要等到完全不可用。很多时候下游还活着,但已经慢到足以拖垮调用方,这时就应该快速止损。
误区三:重试能提高成功率,所以多配几次
重试只适合短暂网络抖动。下游容量不足时,重试会放大压力。写接口还要考虑幂等,否则一次用户请求可能变成多次扣库存、多次扣款、多条消息。
项目实践:按链路重要性定策略
不要所有接口一刀切,要按链路重要性来定策略。
核心链路,比如下单、支付、登录,要有明确的超时、并发上限、熔断阈值和告警。非核心链路,比如推荐、积分展示、活动浮层,可以更积极地降级。
落地时先做三件事:
- 划分核心和非核心接口
- 给每个远程调用设置超时和最大并发
- 给关键下游定义失败后的业务动作
技术配置只是表面,真正难的是业务兜底。比如库存服务不可用时,下单能不能先接收?支付确认失败时能不能异步补偿?用户看到什么文案?运营是否能接受?这些都要提前设计。
容量估算不要拍脑袋。比如下游支付服务稳定承载 800 QPS,订单服务有 8 个实例,理论上每个实例给支付的并发和 QPS 都要有上限,不能让单个实例在异常时无限制重试。粗略估算可以从这几个量开始:下游容量、调用耗时 P95、实例数、连接池大小、业务峰值系数。限流阈值不是越大越好,而是要小于下游真实承载能力。
排查路径:看哪些指标
熔断限流相关问题,最容易从”接口报错”误判成”服务挂了”。排查时先看:
- 是否触发限流,触发维度是用户、IP、接口还是应用
- 熔断器当前状态是关闭、打开还是半开
- 慢调用比例是否先于错误率上升
- 线程池、连接池、队列是否接近水位
- fallback 是否执行成功,是否又压垮了另一个资源
- 重试次数是否导致下游请求量被放大
尤其要注意:fallback 也可能失败。降级逻辑如果还要远程调用另一个服务,那只是把故障从一条链路挪到另一条链路。
如果看到熔断频繁打开又恢复,通常要检查两件事:阈值是否太敏感,以及半开探测是否太激进。如果看到限流触发但系统仍然很慢,要检查限流是不是只挡住了入口 QPS,却没有限制内部并发和队列长度。