什么是高并发怎么解决

接入与开发

讲清高并发到底指什么,并给出一套可落地的限流、排队、超时、降级与观测组合拳,让被流量打崩的开发者能按顺序排查和改造。

接口平时跑得好好的,一到高峰就开始超时、报错、整条链路雪崩,这类问题多半不是代码写得差,而是没做并发控制。这篇先把高并发的含义、瓶颈通常卡在哪一层讲明白,再给一套从测速、限流、排队到降级和日志的落地顺序,你可以照着一条条对照自己的服务改。

接口平时跑得好好的,一到高峰就开始超时、报错,严重的时候整条链路跟着雪崩。很多人第一反应是「代码写得不行」,但真实原因往往朴素得多:没有做任何并发控制,请求想进就进,堆到内存里、堆到线程池里、堆到下游供应商的连接上,最后一起崩。这篇不聊抽象架构,先把高并发的含义和瓶颈常卡在哪一层讲清楚,再给一套从测速、限流、排队到降级和观测的落地顺序,你可以照着一条条对照自己的服务改。

全程只给判断标准和能动手的动作,不给绝对承诺。任何一套方案都有不适用的场景,我会在每节里把「什么情况下不适用」一并写出来,避免你把复杂机制套到根本不需要它的地方,白白增加维护成本。

先把「高并发」这个词拆开看

高并发说的不是请求总量多,而是同一个时间窗口内「同时在处理」的请求数。它和 QPS、吞吐量、延迟是四个不同指标:QPS 是单位时间到达量,吞吐是单位时间完成量,延迟是单个请求耗时,并发数是任意时刻在途的数量。一个日调用量很大的系统,如果请求均匀散在全天,并发数可能并不高;反过来一个瞬时集中打进来的小系统,照样会被打崩。判断自己算不算高并发,先看峰值同时在途请求数、队列是否堆积、延迟分位数有没有在高峰期跳变。

  • 并发数不等于并行数:单核也能扛住高并发,靠的是把等待时间让出去,比如等下游返回时先处理别的请求
  • 峰值在途请求数是核心指标,比日均调用量更能反映系统压力
  • 看延迟分位数而不是平均值,平均值会把少数慢请求掩盖掉
  • 队列开始堆积、延迟随负载上升而持续跳变,通常说明已经接近并发上限
  • 什么情况下不适用:纯离线批处理、低频内部脚本没必要套这套方案,硬上只会让代码更难维护
并发数
同一时刻还在处理、还没返回的请求数量
QPS
每秒新到达的请求数,和同时在途是两回事
吞吐量
每秒真正处理完的请求数,受限于最慢那一环
延迟分位数
比如绝大部分请求的耗时落在哪个区间,比平均值更贴近用户感受

先定位瓶颈在哪一层,再谈方案

被流量打崩之后最容易犯的错,是没定位就直接加机器或提高并发配置,结果把冲击同步放大到下游。正确做法是按链路顺序一段段排查:客户端同时发起多少、网关放进来多少、应用进程的线程和连接池用了多少、下游模型服务或数据库排了多少、上游供应商是不是在限速。任何一环都可能是真瓶颈,找到它之前,所有优化都是盲猜。

典型症状更可能的瓶颈层先看什么
全链路一起变慢,请求都能返回但很慢下游排队或上游限速下游队列长度、供应商限流返回码
只有部分请求报错,其余正常限流触发或连接池耗尽限流触发次数、连接池等待数
报错集中在某个固定时段定时任务与用户请求叠加任务调度时间、那一刻的在途请求数
所有指标都正常但用户说卡客户端或网络环节客户端并发设置、超时配置

用真实数字说话,别凭感觉。先到站内的 测速工具(/tools/speed-test) 量一遍自己服务的延迟基线,知道正常水位在哪,才谈得上判断「慢了多少」。定位清楚再决定优化哪一段,这一步花的十分钟,能省掉后面几小时的无效改动。

限流和排队:先保住服务,再谈体验

限流的核心目的不是拒绝用户,而是让服务活下来。入口先设一个并发上限,超过的请求要么快速失败、要么进队列,绝不能让它们无限堆在内存里。队列必须有边界,同时设队列长度上限和等待超时,等不到就直接返回一个「可重试」的明确错误,让调用方知道该退避,而不是干等到连接断掉。这个「可重试」信号很关键,它是你和调用方之间的约定。

  1. 先分类再限流把接口按重要程度分档,核心链路给足额度,非核心的严格限制。按 Key、按用户、按接口、按供应商分别设限,避免一个重任务吃光所有额度。
  2. 设置队列边界队列长度和等待超时都要有明确数值,超时后返回带重试提示的错误,而不是长时间挂起。
  3. 按额度分档排队不同档位给不同的队列节奏和批次大小,比统一砍并发更平滑。具体取舍可以参考站内 按档位做并发与限流(/guides/concurrency-throttle-by-tier) 那篇里的对照。
  4. 上线后验证观察限流触发次数和成功率变化,确认拒绝的是最不重要的那部分流量,而不是误伤核心请求。

不适用的情况也要说清楚:如果业务本身要求「一个都不能丢」,纯靠拒绝就不合适,得配合排队加上游扩容或异步化改造,把短时峰值摊到更长的时间窗里。限流只是止血,不是万能药。

超时、重试与退避:别把重试写成放大器

故障发生时,重试常常是压垮系统的最后一根稻草。下游只是慢了一点,上游拼命重试,流量翻倍打回来,本来能缓过来的服务直接被打死。所以每一层都要有独立超时:连接超时、读取超时、整体超时分开设,避免下游卡住时上游线程被全部占满。超时值的选取依据是「正常情况下这个请求的耗时分布」,参考站内工具量出来的基线,而不是随手填一个很大的数。整体调用链还要设总预算,超过预算就放弃,别让某一次请求无限拖长。

  • 连接、读取、整体三类超时分开配置,别只设一个总的
  • 重试只对幂等、且重试后有意义的失败做,非幂等写操作不要自动重试
  • 指数退避加随机抖动,避免所有客户端在同一时刻齐刷刷重试
  • 给整条链路设总耗时预算,超预算就放弃并返回明确错误
  • 遇到限流类错误的处理顺序,可以对照站内 限流与退避处理(/guides/429-concurrency-backoff) 里的排查思路
  • 不适用的情况:强一致的资金、权限类写入,重试必须有去重机制,否则会重复扣减或重复授权
重试不是勇气,是放大器;没有退避和预算的重试,等于亲手把故障范围扩大一倍。

降级与兜底:想清楚哪些功能可以先关

降级的前提是提前想清楚「哪些功能可以先不要」。等到系统已经崩了再临场决定关什么,基本来不及。建议提前列一张可降级项清单:非核心的润色、摘要、扩展类能力可以关;核心链路必须保住。降级顺序也要排好:先换更快的模型或更短的输出,再走缓存,再返回简化结果,最后才是明确报错。每一步都要有对应的开关,出事时能立刻切、事后能立刻切回来。

降级层级动作适用条件
第一层缩短输出、换更轻的处理方式整体压力上升但还没到极限
第二层走缓存或返回上一次结果数据时效要求不高的查询类接口
第三层关闭非核心增强功能核心链路已经开始超时
第四层明确返回错误并提示重试已经无法保证正确性,宁可不返回

要强调一句:涉及资金、权限等强一致要求的接口,不能用返回旧数据来兜底,降级只能降体验,不能降正确性。这类接口宁可明确失败,也不要返回看起来正常但已经过期的结果。

缓存与批量:减少无效的重复请求

很多高并发问题,本质是被重复的无效请求拖垮的。相同或高度相似的请求做结果缓存,尤其提示词固定的场景,命中一次就少一次下游调用,这是性价比最高的优化。零散的小请求可以合并成批处理,降低上游调用次数。但批处理有个坑:批量失败时重投的边界要划清楚,别整批重发导致部分内容重复处理。

  • 提示词固定、结果稳定的场景优先加缓存,命中率是核心指标
  • 缓存要设失效策略,别为了扛并发把过期数据一直发给用户
  • 小请求合并成批,但批内单项失败要有独立的失败标记,不能整批重投
  • 长任务异步化:先返回任务号,结果走回调或轮询,别让连接一直挂着
  • 不适用的情况:实时性要求极高、每次结果都必须不同的场景,缓存收益有限,别硬套

观测与压测:改完怎么知道真的有效

做完上面这些改动,必须能回答一个问题:到底有没有变好。这就要靠指标。关键指标至少记这几项:在途请求数、成功率、延迟分位数、限流触发次数、超时次数、重试次数。少了任何一项,复盘时都会缺一块拼图。日志字段要能复盘单次请求为什么慢、为什么贵,站内 日志字段与回放(/guides/api-log-fields-replay) 那篇给了可以直接对齐的字段清单。

  1. 补齐指标在途请求、成功率、延迟分位数、限流、超时、重试六项都要能查到,缺一不可。
  2. 渐进压测找拐点从小流量开始逐步加量,观察吞吐不再上升、延迟开始猛涨的那个点,把它作为并发上限的参考。
  3. 小流量先验证改动先放一小部分流量,确认指标变好且没有新错误,再全量。
  4. 复盘单次请求用日志字段回放几个慢请求,确认慢在哪一层,和当初的瓶颈判断是否一致。

压测的价值不在于跑出一个好看的数字,而在于找到系统的拐点。拐点之前的加量是有效的,拐点之后再加只会让延迟更糟。把这个位置记下来,日常配置的并发上限就照着它的下方留出余量来设。

一份可以直接照着走的排查顺序

最后把整个流程压缩成一份能直接照做的清单。原则只有一条:每一步只改一件事,改完立刻对比指标。同时改好几处,出了问题根本没法归因,最后只能靠猜。下面八步按顺序走,绝大多数被打崩的场景都能从中找到自己的那一环。

  1. 第一步:量基线用站内测速工具量出正常情况下的延迟和吞吐水位,作为后续对比的参照。
  2. 第二步:找瓶颈层按客户端、网关、应用、下游、上游顺序逐段排查,确认到底是哪一环先顶不住。
  3. 第三步:设限流与队列入口加并发上限,队列设长度和等待超时,按 Key、用户、接口、供应商分别限。
  4. 第四步:补超时和退避连接、读取、整体超时分开设,重试加指数退避和随机抖动,整链设总预算。
  5. 第五步:做降级开关提前列可降级项和降级顺序,做成开关,出事能立刻切、事后能立刻切回。
  6. 第六步:加缓存和批处理相似请求缓存,小请求合并,长任务异步化,注意批量失败的重投边界。
  7. 第七步:补齐指标与日志在途请求、成功率、延迟分位数、限流、超时、重试六项指标和可复盘日志都要到位。
  8. 第八步:渐进压测确认小流量逐步加量找拐点,确认改动真的有效,再决定全量。

限流阈值到底设多少,光看压测还不够,得结合真实用量。你可以到 用量核对页面(/usage) 对照自己当时的真实请求量和分布,再回推一个既能保住服务、又不至于误伤太多正常请求的阈值。预算层面的影响同样值得一并看,避免优化做完才发现成本结构变了但没提前评估。

下一步如果不想从零摸索,可以先去看站内整理好的并发与限流实践和常见错误处理,把自己的瓶颈层对进去,再决定先改哪一步,比直接上手调参数省事得多。

常见问题

高并发和 QPS 是一回事吗?

不是。QPS 说的是每秒进来多少请求,高并发说的是同一时刻有多少请求正在被处理,两者中间还隔着每个请求的处理时长。同样 QPS 下,处理越慢在途请求越多,越容易把连接池和线程吃满,所以看监控时这两个指标要分开看。

加机器能解决高并发吗?

只能解决一部分。如果瓶颈在你自己的进程,加机器有用;如果瓶颈在上游供应商的限速、数据库连接数或某个共享锁,加机器反而会把压力更集中地打过去。先去 /tools/speed-test 量出基线和瓶颈层,再决定是扩容还是限流。

限流阈值应该设多少?

没有通用数字,要看上游给你的额度、你自己的资源上限,以及压测测出来的拐点。稳妥做法是取「压测拐点」和「上游额度」里更小的那个,再留一点余量。具体额度口径去 /official-api 和 /usage 对照当时的数字。

排队和直接拒绝哪个更好?

看业务能不能等。能等的走有界队列,设最长等待时间,超时再拒绝;不能等的(比如用户正在等结果的交互请求)直接快速失败并提示可重试,比让用户干等几十秒体验更好。两者可以按接口分开配。

降级会不会让用户觉得功能变差?

会,但比整站不可用强。关键是把降级范围限定在非核心功能,并且提前把开关做好、演练过切换。真正不能降级的强一致接口,宁可直接报错也不要返回旧数据,这一点要在设计阶段就定下来。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。