QPS、吞吐量、响应时间与并发数:性能指标解读与优化实践

发布时间:2026/10/16 8:36:24
QPS、吞吐量、响应时间与并发数:性能指标解读与优化实践
1. 先搞清楚这几个指标到底在衡量什么如果你维护过任何一套线上系统迟早会跟这四个词打交道QPS、吞吐量、响应时间、并发数。只要业务流量一上来监控图上最先报警的几乎都是它们在技术讨论里被混用次数最多的大概率也是它们。很常见的一幕是线上接口变慢了有人说是QPS太高有人说是并发扛不住还有人直接说系统吞吐量不行。这几个说法听起来都像那么回事但又都不够准确最后容易讨论半天也找不到真正的瓶颈。这篇内容就围绕这套指标展开讲清它们各自的定义、之间的定量关系以及真正提高吞吐量时该按什么顺序动手。适合后端开发、运维以及做架构设计的人参考也是我踩过不少坑之后整理出来的经验。1.1 别再把QPS和吞吐量混为一谈先解决最普遍的一个误解QPS和吞吐量经常被当成同一个东西但严格来说它们的口径不一样。QPS全称是Queries Per Second最早更多出现在数据库场景里指每秒执行的查询数。现在业务系统里说到QPS一般就直接代表每秒请求数是一个明确的“速率”概念。比如你看到监控上写着“QPS500”含义是这一秒有500个请求打到了服务上。吞吐量Throughput是一个更宽泛的概念。它能用每秒事务数TPS来衡量也能用每秒请求数来定义还可以是每秒处理的字节数或者消息条数。具体用哪种单位取决于你在衡量哪一层。数据库工程师聊吞吐量时可能看每秒执行的SQL次数网关看每秒转发的连接数消息队列看每秒进出多少条消息。哪怕针对同一个系统“吞吐量”的数字也会因为计量单位不同而不同。所以更准确的沟通方式是把QPS理解成吞吐量的一种常见表达形态二者不能无条件画等号。你在跟别人确认问题时最好先问一句“你说的吞吐量是按请求数算还是按事务数算”很多线上事故的误判就是栽在这种口径不一致上。1.2 响应时间和并发数是彼此的“影子”响应时间Response Time是最直观的指标一个请求发出去到完整响应返回中间消耗的时间就是它。但真正到排障时只看平均值是不够的。某接口平均响应时间200ms听着不错可P99可能已经到了1.5s这意味着每一百个请求里就有一个会明显超时。对于用户来说那个超时请求的感受才是真实的。所以评估响应时间要以百分位为准P95和P99比平均值更有参考价值。并发数则代表系统在某一时刻正在处理的请求数量。它跟响应时间的关系可以粗略理解成“影子”关系响应时间越长同一个线程处理单个请求的周期就越长在每秒新请求数不变的情况下同时“在途”的请求自然就越多。数据库连接池打满、线程池排队本质上是并发请求在资源上堆积了。很多人以为高并发是“来了很多请求”造成的其实有不少情况是响应时间变慢导致系统不得不长时间占用线程和连接才显出并发很高的样子。一个系统能同时处理多少请求不完全取决于“来了多少”而是取决于“每个请求占多久”。1.3 一次排障里怎么用指标定位瓶颈举一个典型的排障过程来说明。某天突然有用户反馈页面转圈监控显示某核心接口的P95响应时间从原来的300ms涨到2sQPS其实没有明显变化只是从800升到了900。这时如果把问题归结为“请求量太大扛不住”很可能就误导了后续方案。正确做法是按数据逐层排查。先看QPS涨幅900对系统来说不算突然排除突发流量。再看平均并发数用下一章会讲到的公式估算平均响应时间约1500ms时在途请求数大约是 900×1.5 1350这个数对线程池和数据库连接池都是压力。然后看数据库监控发现慢查询数量陡增某个SQL的扫描行数从几万涨到了几百万原因是用户一次多选了几个筛选条件导致索引失效。到这里瓶颈就清楚了不是流量压垮了系统而是一条写得不稳的SQL把响应时间拖长进而推高了并发占用。优化SQL后P95回落到350ms吞吐量和并发数自然都回到了正常区间。这个案例也是全篇最想强调的一个理念指标之间会互相传导不要只看表面最高的那个数。2. 指标之间的定量关系与现场测算方法只理解定义还远远不够做性能优化需要有“算账”的能力。系统能支撑多少并发、目标QPS对应的平均响应时间预算应该是多少这些都能通过一个简单的公式推算。现场没有完整压测工具的时候也可以靠日志和监控里的有限数据摸出一个大致结论。2.1 并发数 QPS × 平均响应时间这条公式怎么用在稳定的负载区间内系统内部的请求满足一个经典关系在途请求数 每秒流入的请求数 × 单个请求的平均处理时间。用字母表达就是 L λ × W。这里的λ相当于QPSW是平均响应时间单位秒。举个例子。某个接口平均响应时间是100ms目标QPS是500。那么系统同一时刻需要容纳的并发请求数为 500 × 0.1 50。如果线程池只开了40个线程必然有10个请求在排队响应时间会开始劣化如果数据库连接池只有30个连接那么剩下的20个请求只能干等连接释放。这个公式的工程意义有两个方向。正向用给定目标QPS和可接受响应时间算资源规模。反向用监控上看到当前并发数倒推当前实际处理能力和响应时间是否匹配。比如监控显示服务的活跃线程数长期在200平均响应时间400ms那么实际吞吐量大约就是 200 / 0.4 500 QPS。如果这个数字远低于业务目标说明单请求耗时太长或者资源分配不合理。注意这只是一个稳态估算真实系统会有波动。并发数要有余量不能刚好卡在计算值上否则一个响应时间的小抖动就会引发排队风暴。通常我会按计算值的1.2到1.5倍去准备线程或连接。2.2 没有压测环境时的粗略估算不是所有系统都有条件随时做全链路压测但线上数据总是有的。我常用的笨办法是结合三类数据估算从网关或接入层日志统计最近一小时的请求总量除以3600得到平均QPS。从监控面板读该服务的平均响应时间和P99响应时间。从运维监控读进程活跃线程数、数据库活跃连接数找到并发占用情况。有了这三个数代入上一节的公式能快速判断当前系统的容量余量。例如你观察到QPS峰值是800平均RT是250ms那么高峰期系统在途请求约200个。如果线程池上限是400说明还扛得住如果线程池已经长期在350附近波动意味着加一点流量就会开始排队。这种估算的误差主要来自对“平均值”的依赖。线上QPS和响应时间都有波峰波谷用简单平均值容易低估压力。我会把峰值QPS和P99响应时间再代入一遍得到一个相对冒进的并发数然后以这个数作为容量规划的上界。虽然不够精确但足够用来判断要不要提前扩容。2.3 压测时到底该盯哪些数字有条件压测时很多人只盯着压测工具最后输出的Requests/sec看到数字涨了就觉得系统变快了。这样容易忽略两个关键维度延迟分布和错误率。同样跑出每秒1000个请求如果P99从200ms变成800ms说明系统已经进入排队饱和区再压下去随时可能雪崩。我会按下面的顺序记录数据每个阶段的QPS与吞吐量确认是否还有上升空间。平均RT、P95、P99观察延迟曲线是否出现突然上扬。超时数、5xx比例、连接错误数一旦错误率超过0.1%就要停掉这轮压力。服务端资源CPU、内存、GC、线程数、连接池使用率、磁盘IO等待。压测目标不是压出一个好看的数字而是找到系统从“线性增长”到“开始排队”的拐点。在拐点之前QPS上去、延迟基本平稳过了拐点QPS几乎不再涨延迟和错误却飞速上升。这个拐点对应的并发能力才是你需要写进容量报告里的参考值。3. 提高吞吐量的思路从单请求优化到架构演进明白了指标关系接下来进入最实际的问题怎么把吞吐量提上去。我的基本主张是“按成本从低到高动手”先用最少的改动把瓶颈解除不要一上来就搞架构改造。下面这几条思路是按我自己的实践顺序整理的每一步都有明确目的。3.1 先减后加降低响应时间为什么是第一步提高吞吐量最直接的手段不是猛加机器而是让单个请求更快结束。从公式 L λ × W 可以直观看到平均响应时间W降下来同样数量的线程和连接就能支撑更高的吞吐量λ。说到这里我想起一次真实改动。某个订单查询接口原本会把一个月内某店铺所有订单都查出来再在应用层分页返回。数据量一大SQL扫描行数以百万计平均RT到了2s每秒只能处理几十个请求。改动其实很小把SQL改成条件分页先查当前页的ID再回表查明细同时只返回前端需要的字段去掉大字段的传输。改完后平均RT降到180ms吞吐量直接翻了接近十倍。这个例子就是想说明应用层代码、SQL写法、接口协议里隐藏的性能浪费往往比架构问题更容易拖垮吞吐量。所以在考虑扩容之前先找找有没有这类“明显不合理”的开销。具体可以检查三类点是否有全表扫描或索引失效的SQL、是否有不必要的序列化和大字段返回、是否存在串行调用的远程接口。3.2 让系统同时处理更多请求线程、连接与IO模型单请求耗时优化到一定程度后下一步是提升系统同时处理请求的能力也就是把并行度做上去。并行度首先受线程池影响。CPU密集型任务线程数可以接近CPU核心数再多只会增加切换开销IO密集型任务线程数通常按核心数的2倍起步具体要看阻塞时间比例。很多团队的线程池设置只有一句话“把maxThreads调大”结果调大后吞吐量没涨响应时间反而恶化因为上下文切换吃掉了CPU。比较合理的做法是先按经验公式设定一个初值再通过压测微调。连接池是另外一个容易忽略的并行度瓶颈。数据库连接、缓存连接、下游RPC连接任何一个池被打满都会让请求卡在等待连接的环节。连接池不是越大越好数据库侧能支撑的连接数、连接获取成本和事务时间共同决定了合理范围。比如一个事务平均占库500ms如果想支撑500TPS同一个数据库实例上至少需要约250个连接在同时运行如果数据库配到了这个连接上限继续提高并发只能靠缩短事务时间或减少事务数。IO模型上的选择也会明显影响吞吐量。传统的每请求一线程模型在连接数和读取请求上都会浪费大量内存和切换成本换成基于事件循环的IO模型后同样一台机器能支撑的连接数可以高出一个数量级。这不意味着所有系统都必须马上改造但新项目选型时值得把这笔账算进去。3.3 缓存、异步和限流吞吐量稳定性的三件套并行度做足了还得保证系统在高流量下“不突然变慢”。这里我有三件实用的工具缓存、异步、限流。缓存解决的是重复计算问题。同一份热点数据被并发请求反复读取时每次都去数据库查并重新加工一遍既浪费也慢。把热点数据放到缓存里之后穿透到数据库的请求数量会大幅下降响应时间也由几十毫秒降到几毫秒吞吐量自然上一个台阶。缓存有几个常见坑缓存穿透查询根本不存在的数据每次都打底层存储、缓存击穿热点key失效瞬间大量请求同时打到底层、缓存雪崩大量key同时失效。针对穿透可以用空值缓存或布隆过滤器针对击穿可以用互斥更新或逻辑过期针对雪崩要错开过期时间。异步解决的则是“慢操作占据请求链路”的问题。比如短信通知、报表生成、外部渠道同步这类动作如果都在请求里同步执行用户响应时间会被拖到几百毫秒甚至更久。把这类操作改为异步处理请求进来后立刻写入消息队列并返回“已受理”由后台消费任务慢慢执行。这样前端接口的响应时间变短单位时间内能完成的请求量也随之增加。限流看起来跟“提高吞吐量”矛盾实际上它是保护吞吐量的手段。当流量超过系统承载上限时如果不加控制所有请求都排队超时最终正确的是“全部失败”。如果做一层限流宁可拒绝一部分请求也要让系统在剩余容量内保持低延迟和稳定吞吐。限流可以采用简单计数、滑动窗口、漏桶或令牌桶等不同算法。令牌桶允许一定程度的突发流量同时限制平均速率我更常用。3.4 什么时候该做架构层改造代码优化、缓存和异步都做完了单机容量依然不够时才轮到架构层改造。常见的路线是多实例水平扩容前面挂负载均衡多台服务共同分担流量。数据层拆分先读写分离再把高频数据分库或分表。引入独立中间件用缓存集群、消息队列、搜索引擎等把压力从核心数据库分摊出去。水平扩容最大的敌人是“状态”。如果用户会话、临时数据都放在本地内存扩容后就会出现请求落到不同实例时数据不一致的问题。所以扩容之前要做无状态化改造或者引入集中式会话存储。数据库拆分更要谨慎分库分表带来的分布式事务、跨节点查询和主键策略问题会显著增加开发复杂度。我的原则是能通过缓存和索引解决的不碰分库能通过读写分离解决的不碰分表分表只对真正持续增长且高频访问的核心数据做。4. 一个查询接口的吞吐量优化实录理论铺垫到这里我用一个模拟场景把完整优化过程串起来。假设有一个交易查询接口线上压测成绩是QPS峰值100P95响应时间800ms数据库连接池经常打满。目标是把它优化到支撑800以上QPS同时P95降到300ms以内。整个过程一共四步每一步都有可复现的操作方法。4.1 摸底先用数据确认瓶颈在哪无论接到什么性能任务第一件事永远是“看数据”而不是“猜方案”。我先把问题拆成几条线索用压测工具对接口跑了5分钟固定压力记录当前QPS、RT分布和失败率。打开数据库慢查询日志看两个小时内执行时间超过1s的SQL。观察监控面板里服务线程池活跃数、数据库连接池使用率、GC频率。顺手抓了一次线程转储看看请求卡在哪个方法调用上。结果很清楚慢查询日志里出现一条SQL过滤条件包含一个未加索引的字段单次执行扫描行数近500万。连接池监控显示使用率长期在95%以上说明几乎所有请求都在等待数据库连接。线程转储里大量线程阻塞在获取数据库连接的方法上。到这里我基本确定了优化方向先消灭这条慢SQL再看连接池能不能撑到目标QPS。如果SQL不优化单纯扩大连接池只会让数据库CPU继续飙高问题不会消失。4.2 第一步改动SQL和索引最便宜也最见效针对那条慢SQL做了三件事。第一分析执行计划确认索引确实没被用上。然后新建了一个联合索引字段顺序按照查询条件的区分度从高到低排。建完索引后重新看执行计划扫描行数从500万降到1万级别。第二把SELECT中的字段列表收窄之前是把整行所有列都取出来其中包含几个大文本列。改成了只查询当前列表页需要的核心字段。第三调整了应用层的查询逻辑。原先是一次查出全量结果到内存再分页改为先查当前页ID再用主键条件查详细信息这样数据库端只需要处理当前页的数据量。这三处改动上线后单次接口平均RT从800ms降到220msP95降到380ms。在同样资源下压测QPS从100涨到了350左右。这一步改动的成本极低也只动了SQL和一行查询逻辑收益却非常明显。4.3 第二步改动给热点数据加缓存SQL优化之后接口本身已经不慢了但数据库的查询压力仍然很大。下一个目标是减少真正到达数据库的请求数。看接口数据的特征发现查询结果有很强的时效性要求但对一致性要求不算苛刻允许最多几十秒的延迟。适合做缓存处理。具体方案是把当天的热门交易列表按查询条件生成一个缓存key第一次查询后把结果写入缓存后续请求先读缓存未命中才回到数据库。这里我把缓存穿透和击穿都预防了一下。穿透方面对于查询结果为空的情况也做了短暂的空值缓存避免恶意或异常的无效查询绕过缓存直接打数据库。击穿方面当热点key失效时不直接在业务代码里同步回源而是用一个互斥锁保证同一时刻只有一个请求去数据库加载其余请求短暂等待后读到新缓存。这一轮优化之后数据库侧的读QPS从峰值300降到了不到50接口的P95进一步降到90ms左右。整体吞吐量压测可以稳定跑到650 QPS而且数据库连接池使用率降到了40%上下。4.4 第三步改动让连接池和线程模型匹配业务到这一步数据库已经不再是瓶颈系统重新暴露出新的限制服务端的线程池设置过小而且有几个下游远程调用的响应偏慢拖住了请求线程。原来线程池最大线程数是120压测到650 QPS时线程池已经接近打满活跃线程数长期在110。我在压测过程中逐步上调线程数分别测了150、200、250三组配置观察吞吐量和延迟变化。最终在200线程附近取得平衡QPS在700-800之间P95在220ms附近继续加大到250时CPU上下文切换明显变多P95反而升高到350ms。连接池配置也做了调整。数据库连接数从50提高到80同时把连接获取的超时时间从1s缩短到500ms。这样即使有个别慢SQL或短时抖动线程不会傻等太久能更快失败并触发降级逻辑。这轮调整完成后压测成绩稳定在800 QPS以上P95约为220ms满足预期目标。整个改造过程没有引入新的中间件也没有调整服务数量纯粹靠SQL优化、缓存和资源配置调整完成。5. 常见问题与排查技巧实录无论指标定义了解得多清楚实际排障总会遇到一些反常识的现象。下面几个问题是我在最常被问到的场景每次遇到都有固定套路去处理。5.1 CPU不高但吞吐量上不去这类问题最迷惑人。明明CPU还有大量空闲压测的QPS却上不去延迟还在涨。一般先怀疑三点。第一是不是锁竞争。看线程转储里有没有大量线程处于BLOCKED状态等待同一个锁。如果有说明并发请求在争抢共享资源解决办法是缩小锁范围、改用读写锁或分片锁。第二是不是IO等待。数据库连接池、下游HTTP连接池、磁盘IO都会让线程长时间等待。看监控里的连接池使用率和IO等待时间八成能定位到具体资源。第三是不是GC拖后腿。GC日志如果显示频繁发生Full GC说明堆内存或对象分配有问题。大量短暂对象在新生代不断晋升会导致GC停顿占用CPU。调整堆大小、优化对象分配比提升线程数更有效。5.2 并发数一高响应时间就飙升这通常是系统进入“饱和拐点”的典型信号。每秒请求量没有暴涨但响应时间和并发数同时升高形成了恶性循环。我一般按下面的顺序判断先看线程池队列积压情况。队列从0涨到几百时说明线程处理不过来了。再看线程切换次数和CPU系统态占比如果系统态占用很高说明线程太多切换开销压过了有效工作。最后看GC频率。这三个数据能解释90%的响应时间劣化。解决手段通常是要么降低单请求耗时要么限制入口流量。如果系统已经过载硬撑没有意义先限流让队列清空同时把慢请求降级成快速失败保护整个系统的不再恶化。5.3 加了机器吞吐量却没变听起来很气人以为水平扩容就能提升吞吐量结果机器翻倍、监控纹丝不动。这类问题十有八九是“瓶颈不在无状态服务层”。最常见的是数据库或缓存成为全局瓶颈。无状态服务加到十台所有请求却还是打到同一个数据库数据库的CPU和连接数早在三台机器时就已经满了。这种情况下加服务等于原地踏步。要先观测数据访问层确认数据库连接数、慢查询数和CPU使用率。如果数据库已经满载扩容服务没有任何意义应该先扩展数据库的读写能力。另一个常见原因是负载不均衡。如果新增机器的流量分配不均可能部分机器还在空闲、部分机器已经过载。检查负载均衡策略是否按实际权重分发同时确认服务注册发现是否真的把新实例纳入了。5.4 常见问题速查表把几个高频场景整理成表格方便快速对照。现象可能原因优先排查项常用解法CPU高、QPS低SQL扫描行数过大、死循环、GC频繁慢查询日志、GC日志、执行计划优化SQL、加索引、调整GC参数CPU低、QPS低连接池满、锁竞争、远程调用慢连接池监控、线程转储、下游耗时调整池大小、减少锁粒度、异步化响应时间飙高线程池排队、GC停顿、资源争抢队列长度、GC耗时、系统态CPU限流、扩容、优化创建对象频率加机器无提升全局瓶颈在数据库或缓存数据层CPU、连接数、缓存命中率读写分离、缓存前置、分库分表吞吐量波动大热点key失效、流量倾斜缓存击穿记录、各实例QPS分布逻辑过期、热度均衡、限流降级最后分享一条算不上总结的个人体会处理性能问题时我习惯先把所有指标统一到“在途请求数 到达率 × 处理时间”这个框架下看一遍谁的数据明显不合理谁就是突破口。多数情况下吞吐量上不去不是某个单一组件不行而是某个请求或某条链路把自己的资源占用拉高把整个系统的余量吃掉了。你不需要一开始就懂特别高深的理论先把慢请求变快把多余请求挡在门外再把并发资源调到合适大小这套组合拳足以解决大多数线上性能问题。