你可能遇到过这种场面:接口压测数字上不去,加了线程反而更慢;或者一批任务开了几十个协程,结果下游把限流砸出来。问题往往不在代码写错,而在一开始就把「并发」和「并行」当成同一件事来设计了。这篇不讲概念定义,只讲在什么负载下该用哪种模型、怎么判断自己选错了,以及验证与回退的顺序。
接口压测数字上不去,加线程反而更慢;一批任务开了几十个协程,下游直接甩限流。这类场面十有八九不是代码写错了,而是一开始就把「并发」和「并行」当成同一件事来设计。前者的目标是别让执行单元空转,后者的目标是让多份算力同时干活,两者服务的是完全不同的负载形状。
这篇不讲概念定义,只讲怎么一眼看出手上的活属于哪一类、该配哪种模型、配错之后按什么顺序排查,以及上线前怎么量、出事怎么退。所有具体数值都以你现场量到的为准,别拿别人的结论套自己的链路。
先看你手上是什么形状的负载
第一步不是选框架,是把待办任务分成两类。等接口、等数据库、等模型返回,这些都是等待型,执行单元大部分时间在挂起,值钱的是同时挂起多少路。压缩、加解密、批量数值计算属于计算型,执行单元一直忙着跑指令,值钱的是有几个核在真干活。判断错类别,后面所有调优都是白费。
| 观察项 | 等待型任务 | 计算型任务 |
|---|---|---|
| 单次任务时间大头 | 网络往返、下游处理 | 本机 CPU 指令 |
| 加执行单元的效果 | 吞吐先涨后平,受下游配额限制 | 吞吐近似线性上涨,直到核数打满 |
| 加错会怎样 | 线程多了只增加上下文切换和内存占用 | 协程开再多也不缩短单步耗时 |
| 优化抓手 | 连接池、批量化、限流与退避 | 切片策略、核数、语言运行时、算法本身 |
- 分档压并发度从低到高逐档提升并发度,每档记录吞吐与 P99 延迟两条曲线。
- 读曲线判断瓶颈吞吐先涨后平,说明卡在资源池或下游配额;吞吐不涨而延迟猛涨,说明卡在下游服务或某把锁。
- 量出外部约束把连接池上限、单机文件句柄、下游给的速率配额这几个数拉出来,取最小值作为并发度上限参考。
并发靠什么实现:调度、队列与限流位
协程、异步 IO、线程池,本质都是同一件事:让等待不占住执行单元。选哪个主要看你用的语言生态和现有依赖是不是阻塞式,硬把整条调用链改成异步,收益常常抵不过新引入的一堆坑。并发度也不是越高越快,真正决定上限的是连接池、外部配额、文件句柄这些外部约束。
- 并发度打满之后,新请求是排队、还是直接拒绝、还是降级返回兜底数据,要提前定死,不留灰度空间
- 队列要有界,无界队列会把压力从下游转移到内存,崩得更安静
- 给排队时长单独打点,它是判断「到底谁在拖」的第一手材料
- 别在同一进程里既跑高并发请求又跑计算型批处理,两者抢的资源完全不同
并行不是免费加速:拆分开销与结果合并
并行只在任务能切成互不依赖的块时才有收益。切片、分发、合并、失败重试这四段本身都要花时间和资源,任务粒度太细时,省下的计算时间会被这四段开销吃回去。所以并行方案里,粒度是个必须反复调的参数,不存在一劳永逸的切法。
- 切片开销
- 把大任务切成小任务本身的时间,切得太细会反噬
- 合并顺序
- 结果按什么顺序拼回,顺序错乱会让上层逻辑难以排查
- 部分失败
- 某几片失败时是整体重试、还是只重试失败片,要提前定
- 异常冒泡
- 子任务的异常怎么汇总回主流程,别丢在日志里成孤儿
还有一个常被忽略的点:并行度上去之后,日志会交错、时间戳会乱、堆栈会缺。如果排查手段没跟上,出问题时你连「是哪一片拖慢了整体」都说不清。所以并行方案要配一套能按任务 ID 串联的埋点,这属于必做项,不是加分项。
什么情况下这套判断不适用
有些场景不是调并发度能救的。任务之间有严格先后依赖,后一步要用前一步的全部结果,这种链条再怎么加并发也快不了,该优化的是单步耗时。下游是按总量共享配额的外部服务,并发和并行都得先让位给配额分配,先做限流与退避设计。
纯离线批处理又另当别论。这类场景允许跑很久,通常优先考虑简单可控的串行加断点续跑,而不是把并发度堆高——跑得久没关系,跑挂了要从头来才要命。所以「要不要并发」这个问题,要先问清楚可接受的失败代价是什么。
上线前的验证与回退清单
验证的核心是拿到可复现的延迟基线,再分档调并发度。不要拿一个凭感觉的数字当结论,也不要把压测环境里的表现直接等同于线上。测速或探针类工具能帮你先量出链路的基础延迟,压测再在这个基线上做增量对比。
- 量基线先用探针类工具测出单次调用的延迟构成,记下等待与计算各占多少。
- 分档压测按并发度分几档压,每档同时记吞吐、P99、错误率,画出拐点。
- 埋点齐全日志里要能还原排队时长、执行时长、下游等待时长、重试次数四项。
- 留远程开关并发度做成可远程调整的配置,出事时先降到保守档位止血。
- 事后复盘止血后再分析是并发结构问题还是下游问题,别一边救火一边改结构。
| 观察信号 | 可能原因 | 先动哪里 |
|---|---|---|
| 吞吐不涨、延迟涨 | 下游配额或锁竞争 | 降并发度,加限流与退避 |
| 吞吐涨后走平 | 资源池或连接池到顶 | 调池大小,做批量化 |
| CPU 高但吞吐低 | 误把等待型当计算型处理 | 减少线程,改异步或协程 |
| 偶发超时、日志对不上 | 并行的部分失败没处理好 | 补任务 ID 埋点,定义重试策略 |
并发是让等待不白等,并行是让算力一起算;把两者混用,调优就变成了猜谜。下一步想知道自己常用的模型和中转服务在下游限流上是什么脾气,可以去站内的中转站与价格追踪页面核对当时的数字和状态,再回来定你的并发度上限。→
常见问题
并发和并行是不是同一个东西的不同叫法?
不是。并发说的是同时处理多件事的能力,重点是任务之间交错推进、互相不阻塞;并行说的是同一时刻真的有多个执行单元在跑。单核机器上也能写并发程序,只是不会有真正的并行。落到工程上,等待型负载靠并发拿吞吐,计算型负载才需要并行拿时间。
我把并发度调高但吞吐没涨,该从哪里查?
按「排队时长 → 执行时长 → 下游等待时长」的顺序看日志。排队时长高说明执行位不够,执行时长高说明卡在本地计算或锁,下游等待高说明瓶颈在别人那里,加并发只会让两边都更糟。先定位是哪一段,再决定是调池子大小还是加退避。
后端服务里是不是并发度越高越安全?
恰好相反。并发度是占用下游配额和本机资源的承诺,调高就等于承诺更多。压测里表现好不代表线上安全,因为线上还有别的调用方在抢同一份配额。稳妥做法是给并发度设上限、给队列设长度、给失败设退避,并留一个能远程调低的开关。