限流与降级的组合拳:过载时的取舍顺序
WhiteCoat
5 min read
判断:系统过载时,先挡住入口流量,再以并发上限节流,非核心功能留到扛不住时才降级,顺序不能乱。一上来就降级核心链路,等于在没挡住洪水前先拆自家墙;只限流不降级,突发流量会卡在半路耗尽线程池。
三件事分别解决什么
限流(rate limit):控制单位时间进来的请求数,超出直接拒绝。解决"入口被冲垮"。
降级(degrade):关闭或简化非核心功能,腾出资源保核心。解决"资源不够分"。
熔断(circuit breaker):依赖持续失败时快速失败,避免雪崩。解决"下游拖死上游"。
三者互补而非替代。把熔断当成限流的一种特殊形式也行,但语义上它针对的是"依赖已坏"而非"量太大"。
取舍的优先级
边缘拒绝优先于内部节流。能在网关、接入层用令牌桶挡掉的请求,不要放进业务线程池排队——排队本身也吃内存和连接。
请求 → 网关限流(令牌桶) → 若超限返回429
→ 业务线程池(有界队列) → 依赖调用
→ 若依赖延迟升高 → 熔断打开 → 快速失败
→ 若系统负载超阈值 → 降级非核心(关推荐/关头像)
典型顺序是:限流截掉最外层超额流量 → 线程池/信号量限制并发 → 熔断隔离坏依赖 → 非核心功能留到扛不住时才降级。降级是保底手段,不是第一反应。顺序颠倒的典型后果:先降级关了缓存预热,导致剩余请求全部回源数据库,反而把数据库压垮。
限流算法与边界
令牌桶允许短时突发(桶里有存量就放行),更适合有波峰的 Web 流量;漏桶强制恒定速率,适合保护下游脆弱依赖。滑动窗口计数能更平滑地控制均值,但实现稍重。
边界条件:限流阈值不能拍脑袋。经验上先取日常峰值的一个倍数作为初始上限,再据拒绝率回退调参;多实例部署时要区分"单实例限流"和"集群总限流",否则扩缩容会误杀或漏放。按用户维度限流(如每用户每秒 N 次)比全局限流更能防单用户刷接口,又不误伤正常用户。
熔断的三个状态
限流是对"量"的控制,熔断是对"质"的响应。依赖错误率超过阈值,熔断器从 closed 切到 open,后续调用直接失败而不发请求;经过冷却期进入 half-open,放少量探针,成功则恢复 closed,失败则回到 open。状态机避免了对已故障依赖的无效重试。
breaker:
errorThreshold: 50% // 错误率超 50% 触发
openDuration: 10s // 打开后冷却 10 秒
probeRequests: 3 // 半开放 3 个探针
要注意:熔断打开后返回的快速失败,调用方要有兜底(返回缓存、走默认逻辑),否则用户看到的是一片报错而非降级内容。
降级该砍什么
降级清单要提前定义,按"对业务影响小"到"影响大"排序:
- 关个性化推荐、关埋点上报
- 关非关键头像/动效
- 列表页返回缓存快照而非实时计算
- 写操作暂存异步队列,稍后补处理
核心交易链路不在降级范围——它降级了系统就失去存在意义。降级开关必须可热开启,不能靠发版生效,否则故障时要等发布流程,黄花菜都凉了。
限流要分层次布防
限流不是只在网关做一道就够。接入层按 IP 或用户限流防刷;服务层按接口容量限流,防单接口打爆线程池;依赖层按连接池或并发信号量限流,防下游被拖垮。三层阈值要自洽:入口放行 1000 QPS,下游依赖实际只能扛 300,中间必须有一层把超额挡下或排队,否则限流形同虚设。每层限流暴露的指标(拒绝数、排队数、限流耗时)要进监控,容量调参靠这些数字而非感觉。
限流与用户体验
被限流的请求返回要带 Retry-After 头,告诉客户端多久后重试,避免客户端盲目重试放大流量。限流阈值的调整要配合监控观察拒绝率曲线,而不是一次改到位,否则容易在"误杀正常流量"和"没挡住洪峰"之间来回横跳。
踩坑
把 429 和 503 混用:限流拒绝应返 429(你被限了),系统过载兜底才返 503(服务暂不可用)。客户端据此决定退避策略。
降级开关没有分级:要么全开要么全关,突发时切一刀反而引发更大抖动。按功能维度做细粒度开关,才能精准腾资源。
只在入口限流、下游无限流:入口放了 1000 QPS,但某个第三方依赖实际只能扛 300,超额请求在内部阻塞拖垮线程池。每个脆弱依赖都要有自己的并发上限。
熔断后无兜底:依赖挂了直接把错误抛给用户,比降级更糟。熔断器打开时应自动切到默认逻辑或缓存。
1 view
0 likes
0 comments