|

Aimee

Write the Code. Change the World.

高并发三板斧:限流、熔断、降级

· 分享镜

大促零点一到,流量瞬间翻几十倍;或者某个依赖的下游悄悄挂了,请求一个个堆在那儿傻等——这两种情况,都能让一个平时跑得好好的系统,在几分钟内被压垮,甚至连环雪崩、整片崩掉。这块我自己刚补的时候也绕了好几遍:三个词听着像近义词,真分起来又总搞混谁管谁。

这篇把"限流、熔断、降级"这套扛流量保命的三件套讲清楚:各自是什么、为什么需要、在什么业务场景下出场、业界拿什么做,以及三者怎么配合。它俩接着前面的伏笔——《缓存》篇讲到缓存雪崩时,最后那道防线就是"限流 / 熔断 / 降级";《消息队列》篇的"削峰"也是同一个目标(扛住洪峰)的另一种打法。这篇就把这道防线本身拆开看。


一、限流:给系统装个阀门,不让洪峰冲进来

是什么。 限流(Rate Limiting)就是给系统装一个阀门:单位时间内只放过固定数量的请求,超过的部分直接挡在门外(拒绝或排队)。它不解决"怎么处理得更快",而是从源头上控制进来的量,让系统始终工作在自己扛得住的水位线以内。

为什么。 因为系统的处理能力是有上限的——一台机器每秒能处理多少请求,是被 CPU、内存、数据库连接数这些实打实的资源卡死的。流量一旦超过这个上限,不限流的结果不是"慢一点",而是雪崩式地全崩:请求排队 → 响应变慢 → 上游超时重试 → 流量进一步放大 → 彻底打死。

一句话点破:限流的本质,是宁可干脆拒绝一部分请求,也不让所有请求一起死。放进来 100% 然后全崩,不如放进来 60% 把这 60% 服务好。

实际业务场景。 几个典型:

  • 秒杀 / 抢购:商品就 1000 件,瞬间却涌进来几百万人。真没必要让几百万请求全砸到库存数据库上——前面挡一道限流,放过远超库存量的一小批进去争抢就够了,剩下的直接告诉用户"活动太火爆,稍后再试"。
  • 开放 API:对外提供接口时,给每个调用方限定配额(比如每个 API key 每秒最多 100 次),防止某一家把整个服务的资源吃光,连累其他人。
  • 爬虫 / 异常流量防护:某个 IP 在短时间内疯狂刷接口,大概率不是正常用户,按 IP 维度限流挡掉,既省资源也防刷。

业界怎么做。 限流主要是两件事:用什么算法算"超没超",在哪一拦。

先看算法,业界常用四种:

算法怎么算特点
固定窗口每个固定时间段(如每秒)单独计数,过了就清零实现最简单;但窗口临界点会出问题——前一秒末尾和后一秒开头挤在一起,瞬间可能放过两倍
滑动窗口把时间窗口切成更细的小格,随时间平滑移动解决了固定窗口的临界问题,代价是记录更多、算起来更重
漏桶(Leaky Bucket)请求先进桶,再以恒定速率漏出去处理;桶满了就溢出(拒绝)输出绝对平稳,适合需要"匀速"的下游;但应对不了正常的突发
令牌桶(Token Bucket)以恒定速率往桶里放令牌,请求得先拿到令牌才能进;桶里能攒令牌允许一定突发(平时攒的令牌可以一下子用掉),又有长期均值上限——最常用的一种

漏桶和令牌桶最容易混:漏桶管的是"出去的速度恒定",令牌桶管的是"进来要先拿令牌、但允许把攒下的额度一次花掉"。所以要绝对平稳选漏桶,要兼顾平稳和突发选令牌桶。

再看落在哪一层,业界的常见组合:

  • 网关 / 接入层限流:在 Nginx、APISIX、Kong 这类网关上配限流规则,把流量挡在进入业务系统之前——挡得越靠前,后面越省。
  • 应用层限流框架:在服务内部做,开源里 Sentinel(阿里开源)是这块很主流的一个,既能限流也能做后面讲的熔断降级;Java 生态里也常用 Resilience4j、Guava 的 RateLimiter。
  • 分布式限流:多台机器要共享一个总配额时,常用 Redis + Lua 脚本把"计数 + 判断"做成一个原子操作(《缓存》篇提过 Redis 适合做这种短命计数 + 原子操作)。
  • 超过限额时,HTTP 上业界约定俗成返回 429 Too Many Requests,让调用方知道是被限流了、可以稍后重试。

注意事项。

  • 阈值怎么定是最难的。定高了形同虚设,定低了误伤正常用户。常见做法是先压测出系统的实际容量,留一定余量当阈值,再根据线上表现慢慢调——很难一次拍准。
  • 单机限流 vs 分布式限流。单机限流(每台机器各管各的)简单、没有额外依赖,但 10 台机器各限 100,总量就漂在 1000 上下、不精确;要精确控制全局总量,就得上分布式限流(共享 Redis 计数),代价是每次请求多一次网络往返,且 Redis 本身成了关键依赖。先想清楚要的是"大致够用"还是"精确总量"。
  • 给用户的反馈别太粗暴。被限流的请求,与其甩一个冷冰冰的错误,不如给个友好提示("当前人多,请稍候")或排队页;前端拿到 429 也最好别立刻无脑重试,否则会把限流住的压力又顶回来。

二、熔断:下游坏了就快速失败,别一直傻等

是什么。 熔断(Circuit Breaking)就像家里电路的保险丝:当它发现某个下游依赖持续出错或超时,就自动"跳闸"——接下来一段时间内,对这个下游的调用直接快速失败,根本不再真的发出去,等过一会儿再试探它好没好。

为什么。 为了防止一个坏掉的下游,把整条调用链拖垮——这正是雪崩在调用链上的样子。设想订单服务要调用支付服务,支付服务突然变慢(每个请求卡 10 秒才超时)。如果不熔断,会发生什么:

每个调用支付的请求都得干等 10 秒 → 订单服务的线程一个个卡在那儿等 → 线程池很快被占满 → 订单服务自己也没法响应新请求了 → 再上游的服务调订单又开始卡……一个下游的慢,顺着调用链一路拖死上游,这就是雪崩。

熔断的思路是:与其让大家排队陪一个坏掉的下游一起死,不如趁早认栽——快速失败把线程立刻释放出来,让系统至少还能处理那些不依赖这个下游的请求。

实际业务场景。 最典型的就是依赖外部第三方的时候:

  • 接入的第三方支付网关抽风了,响应又慢又频繁报错——熔断器跳闸后,这段时间不再真的去调它,直接快速失败,把资源留给别的事。
  • 依赖的短信 / 验证码网关挂了,登录注册流程里发短信这一步,熔断后快速失败,配合后面讲的降级走兜底,别让发短信卡死整个登录。
  • 微服务内部某个非核心下游服务频繁超时,熔断把它暂时"摘掉",保住调用方自己不被它连累。

业界怎么做。 熔断器的核心是一个三态状态机,这是理解熔断的关键:

状态含义行为
关闭(Closed)正常状态请求照常放行,同时统计错误率
打开(Open)已跳闸错误率超过阈值就切到这里,所有请求直接快速失败,不再真调下游
半开(Half-Open)试探恢复打开一段时间后,放少量请求去探一探:成功就切回关闭,还失败就继续打开

这个"关闭 → 打开 → 半开 → 关闭"的循环,既能在下游坏掉时果断断开,又能在它恢复后自动接回,不用人工干预。

工具上,Sentinel、Resilience4j 这些既做限流也做熔断(一套搞定);早年 Netflix 的 Hystrix 是熔断的经典开源实现、影响很大,不过它已经停止新功能开发(进入维护模式),新项目业界一般转向 Resilience4j、Sentinel 这些后来者——提一句这段历史,是因为不少老资料还在讲 Hystrix。

注意事项。

  • 熔断阈值要平衡灵敏和误判。太灵敏(错一两次就跳)会因为偶发抖动频繁误熔断;太迟钝又起不到保护作用。通常按"一段时间内的错误率或慢调用比例"来判定,而不是单看一次失败。
  • 半开探测要小心。半开时只放极少量请求去试,别一下放太多——万一下游其实还没好,这批请求又会触发新一轮拖累。
  • 熔断之后得有人接盘。熔断只是"快速失败",它本身不产出结果。快速失败之后这个请求怎么办?这就接到了下一节的降级——熔断负责"断",降级负责"断了之后给个兜底"。两者几乎总是配套出现。

三、降级:扛不住时弃车保帅,牺牲非核心保核心

是什么。 降级(Degradation)是指系统压力大或部分功能不可用时,主动牺牲掉非核心的功能,把有限的资源集中保住核心主流程。说白了就是弃车保帅:大促扛不住时,宁可暂时关掉一些锦上添花的功能,也要保证下单、支付这条命脉走得通。

为什么。 因为资源是有限的,关键时刻不是所有功能都同等重要。一个电商系统里,"下单支付"是命根子,而"商品评价""猜你喜欢""个性化推荐"是锦上添花。当系统整体扛不住时,与其雨露均沾、最后一起垮,不如把算力、数据库连接这些资源从非核心功能那里腾出来,优先供给核心链路。

一句话点破:限流和熔断是"挡"和"断"——把压力挡在外面、把坏依赖断开;降级是"舍"——在资源不够分时,主动放弃一部分,保住最该保的那部分。

实际业务场景。

  • 大促降级:活动高峰期,临时关掉商品详情页的评价模块、首页的个性化推荐、各种实时排行榜,把省下来的资源全力保下单和支付——用户这会儿也顾不上看推荐,先抢到、付掉钱才是正事。
  • 依赖故障降级:商品页要展示的某个非核心数据(比如"历史最低价")所依赖的服务挂了,就降级成不显示、或显示一个兜底默认值,而不是让整个商品页打不开。
  • 读降级到缓存 / 兜底数据:数据库压力过大时,某些查询直接返回缓存里的旧数据、甚至一份静态兜底数据,牺牲一点实时性,换主流程活着。

业界怎么做。 降级更多是一种架构和预案上的设计,而不是某一个开箱即用的工具,常见几种形态:

  • 开关降级(手动):提前在配置中心(如 Nacos、Apollo)里给非核心功能埋好降级开关,出事时运维一键打开,瞬间把这些功能"关小灯"。这是大促最常用的兜底手段。
  • 自动降级:和限流、熔断联动——一旦触发熔断或限流,自动走预先定义好的降级逻辑(返回默认值、走兜底分支),不用等人反应。Sentinel 这类框架支持把降级规则和熔断绑在一起配。
  • 兜底数据 / 默认值:给关键接口准备一份"实在拿不到真数据时返回什么"的兜底——空列表、缓存旧值、一份预先算好的静态数据,保证调用方至少拿到一个能用的结果,不至于报错。

注意事项。

  • 降级预案要提前定,不能等出事了现想。哪些功能是核心、哪些可降、降级后返回什么、开关在哪——这些都得在风平浪静时就梳理清楚、演练过。真到了高峰出事那一刻,是没时间从头设计的。
  • 想清楚哪些能降、哪些绝对不能降。展示类、推荐类、统计类通常能降;但涉及钱、库存、订单状态这种核心一致性的环节,降级要极其谨慎——降错了可能比不降损失更大(比如超卖)。
  • 降级也要能优雅地恢复。高峰过去后,得有清晰的流程把降级开关关回去、把功能恢复满血,别让系统长期"残血"运行还没人发现。

四、三者怎么配合:一道纵深防线

限流、熔断、降级很容易被当成三个独立技巧,但它们其实是同一道防线上、站在不同位置的三道关卡,目标是一致的——在异常情况下,让系统别整个崩掉。把它们串起来看:

  • 限流站在最前面——让坏流量"少进来"。 这是第一道闸,在请求进入系统之前就按水位线放行,从源头控制总量。挡在最前,后面每一层都跟着轻松。
  • 熔断站在调用链中间——让坏依赖"别连累"。 流量进来后,如果某个下游坏了,熔断负责果断把它断开、快速失败,不让一个坏点顺着调用链拖垮一片。
  • 降级站在最后兜底——扛不住时"弃车保帅"。 当限流和熔断都触发、系统确实吃紧时,降级出场,主动舍掉非核心,把最后的资源保给核心主流程。

一句话点破:限流管"进来多少",熔断管"坏的别扩散",降级管"实在不行保哪个"——一个控源、一个隔离、一个保底,合起来才是完整的高并发防护。

它们和前面两篇也是一条线上的:《消息队列》篇的"削峰"是把洪峰先堆进队列、让下游慢慢消化(削峰填谷),和限流一样是为了不让下游被瞬时洪峰冲垮,只是一个"挡在外面"、一个"堆起来慢慢放";《缓存》篇讲缓存雪崩时,最后那道"缓存挂了也别让数据库全崩"的防线,靠的正是这里的限流 + 降级。这一篇,就是把那道防线本身讲透。

五、AI 服务,尤其吃这三板斧

现在多了一类新服务:调大模型的 AI 服务。大模型这个"下游"又慢(生成要几秒)、又贵(按 token 烧钱)、又不稳(限流配额、超时、过载是常态),把"下游不可靠"放大了好几倍——三板斧基本是标配:

  • 限流:双向——对外控并发(别把成本和 GPU 打爆),对上游大模型 API 也要客户端限流,别超它配额。
  • 熔断:大模型超时是常态,跳闸快速失败,别让请求全卡在"等它回话"上、占满线程池。
  • 降级:这一环最有戏——降到更小更快的模型、返回缓存过的相似回答、退回规则兜底,或直接"AI 有点忙,稍后再试"。

AI 服务不是"要不要上三板斧",而是"打地基就得设计进去"。

六、让 AI 帮你做高并发设计,该怎么提要求

AI 写的代码"能跑"不等于"扛得住"——你让它"写个秒杀接口",它给的大概率没有限流、防超卖、下游兜底。差别全在你怎么提要求:

  • 带上量级和约束,别只说"写个接口":"扛 1000 QPS、库存有限要防超卖、支付下游可能超时"——AI 吃具体约束,不吃"高性能"这种空话。
  • 先让它出方案、讲风险,再写代码:你 review 思路(架构层),比直接收代码靠谱。
  • 拿三板斧当 checklist 逼问:并发安全吗?限流了吗?下游挂了有熔断 + 降级吗?
  • 关键路径(钱、库存)别全信 AI,自己再过一遍。

你越懂三板斧,越知道该要求 AI 啥、审它哪里——所以原理还得自己学。

七、一张表:三板斧速查

把全文收进一张表,这也是最该记住的对照:

限流熔断降级
一句话给系统装阀门,控制进来的量下游坏了快速失败,不傻等牺牲非核心,保住核心
解决什么问题流量超过系统容量,被洪峰压垮一个坏下游顺着调用链拖垮全局(雪崩)资源不够分时,核心主流程被非核心拖累
触发时机请求速率超过阈值下游错误率 / 慢调用超过阈值系统整体吃紧,或被熔断 / 限流联动触发
站位最前(入口)中间(调用链上)最后(兜底)
核心动作拒绝 / 排队超额请求(返 429)跳闸,直接快速失败关非核心、返兜底数据 / 默认值
典型方案令牌桶 / 漏桶;网关(Nginx/APISIX)、Sentinel、Redis+Lua熔断器三态;Sentinel、Resilience4j开关降级(配置中心)、自动降级、兜底数据

名词解释

  • 限流(Rate Limiting):单位时间内只放过固定数量的请求,超额的拒绝或排队,从源头控制进入系统的流量。
  • 固定窗口 / 滑动窗口:两种基于"时间窗口计数"的限流算法;固定窗口简单但有临界问题,滑动窗口把窗口切细、更平滑但更重。
  • 漏桶(Leaky Bucket):请求以恒定速率漏出处理,桶满即拒,输出绝对平稳,但不容忍突发。
  • 令牌桶(Token Bucket):恒定速率发令牌、请求需先取令牌,允许用攒下的令牌应对突发,又有长期均值上限——最常用的限流算法。
  • 429 Too Many Requests:HTTP 状态码,表示请求被限流,调用方可稍后重试。
  • 熔断(Circuit Breaking):发现下游持续出错 / 超时就自动"跳闸",一段时间内对它的调用直接快速失败,像电路保险丝。
  • 熔断器三态:关闭(正常放行 + 统计)、打开(已跳闸,全部快速失败)、半开(放少量请求试探下游是否恢复)。
  • 快速失败(Fail Fast):不再真发请求干等超时,而是立刻返回失败,把资源(如线程)尽快释放出来。
  • 降级(Degradation):压力大或部分功能不可用时,主动牺牲非核心功能,把资源集中保住核心主流程,即"弃车保帅"。
  • 兜底数据 / 默认值:拿不到真数据时返回的替代结果(空列表、缓存旧值、静态数据),保证调用方至少拿到能用的结果。
  • 雪崩:局部故障(缓存失效、下游变慢)经由排队、重试、调用链层层放大,最终拖垮整个系统的连锁崩溃。
  • 削峰填谷:把瞬时高峰流量先堆进队列,让下游按平稳速度消化,与限流同为"扛洪峰"的手段(见《消息队列》篇)。
  • Sentinel:阿里开源的流量治理组件,限流、熔断、降级一套都能做,应用层很主流的一个选择。
  • Resilience4j:Java 生态轻量级的容错库(限流 / 熔断 / 重试等);早年经典的 Hystrix 已进入维护模式,新项目多转向它或 Sentinel。

本文属《研发都要懂的事》·服务端架构设计系列——高并发三板斧。它接住了《架构演进》开篇那张表里"读压力大 / 流量洪峰"一行留下的问题,也和《缓存》篇的雪崩、《消息队列》篇的削峰连成同一道防线。完整代码与系列在 GitHub · backend-notes

评论0

登录后参与评论。

还没有评论,来抢沙发吧。

回到顶部