1. 压力测试到底在解决什么问题1.1 你测的可能不是压力测试这几年做服务端开发我越来越觉得“压力测试”这四个字被用得太泛了。很多人把并发发几个请求、看一眼响应时间跑得慢不慢就叫做压力测试。实际上压力测试、负载测试、稳定性测试、突发测试是四件不同的事目的和场景都不一样。用大白话区分一下负载测试在预期内的并发和流量下系统能不能达到约定的性能指标。比如活动预估每秒 1000 个请求那就按 1000 来测看响应时间、错误率是否达标。压力测试把负载往死里加一直加到系统撑不住为止。关心的是系统的极限在哪、最先崩掉的是哪个组件、崩溃的时候是优雅降级还是直接雪崩。稳定性测试用偏高的负载持续跑几小时甚至几天看内存泄漏、连接泄漏、日志磁盘增长这类随时间累积的问题。突发测试模拟流量在几秒内从低位飙升到高位。这种场景最考验自动扩容、缓存预热、连接池初始化这些“不热身直接开干”的能力。如果你把一个系统用 100 并发跑 3 分钟发现没报错就下结论“必然支持 100 并发”那很可能连负载测试都算不上。压力测试的核心动作是“加压到出问题”并且搞清楚问题发生在哪一层。1.2 核心目标回答三个问题我每做一个压测方案心里都在围绕三个问题转第一个系统真实的处理上限是多少不是估算值不是架构师拍脑袋给的数字而是通过实测得到的一个可复现的结果。第二个瓶颈在哪里并发上涨之后先扛不住的是应用进程、数据库连接池还是 Nginx 后面的某个下游接口瓶颈的位置决定了后续扩容应该加机器还是调参数。第三个系统是怎么坏的有些系统并发高了之后是响应变慢有些是开始报错有些是直接把机器 CPU 打满导致整个节点无响应。你至少要提前知道最坏情况长什么样而不是等活动当天第一次见到。我之前参与过一个偏交易类的项目开发阶段大家觉得“500 并发肯定没问题”结果一压数据库连接池在 300 并发时先耗尽应用线程全部阻塞在等待连接上。如果不做压力测试这个问题大概率只能在真实流量进来之后以半夜告警的形式被发现。这就是压力测试存在的意义用可控的代价把不可控变成可控。2. 压测之前必须想清楚的事2.1 从业务指标反推测试模型很多人拿到压测任务第一反应是写脚本、开并发这是错误顺序。压测模型必须先来自业务场景不能来自测试人员的想象。你需要先回答几个问题接口平时的 QPS 是多少大促或活动期间的峰值大概是平时的几倍读写比例是多少用户是从 App 直接进来还是经过网关、消息队列削峰这些信息决定了测试模型的并发数、请求配比和压测时长。我之前接过一个压测需求对方只说“新系统要扛住大流量”。我追问了半天才搞清楚真实场景里 80% 请求是查询只有 20% 是写入而且写入集中在几条核心数据上。如果压测时全按纯查询去打测出来的结果和真实情况完全对不上。没有历史监控数据时可以从业务预估反推。比如预期日活 10 万核心动作每天人均触发 20 次一天的总请求量就是 200 万。按流量集中在一天内 20% 的高峰时段来算秒级 QPS 大概是 200 万除以86400 乘以 20%约等于 116。再乘一个 3 到 5 倍的安全系数基本就能得出压测目标量级。算出来的数字不一定精确但至少比凭空拍脑袋靠谱。2.2 环境、数据、基线一个都不能少压测环境的选择直接影响结论。生产环境当然最真实但风险高必须在业务低峰期申请窗口做好监控和止损措施。预发环境如果配置和生产不一致测出来的结果只能做参考不能直接当成生产容量。压测前还要把测试数据准备好。这里最容易被忽略的是数据量。如果你的库里只有 1 万条数据所有查询都能命中内存缓存压测结果一定很好看。真实的线上库可能已经积累了上千万条数据索引、缓存命中率、磁盘 IO 完全不是一个量级。所以我在压测前会做这几件事确认数据库数据量、关键表的数据分布和线上形态是否接近把用户 ID、商品 ID、订单号这类参数做成独立的数据池避免所有请求都打同一批数据准备独立的压测账号或压测标记字段方便压测结束后清理脏数据先跑一轮单接口小并发做基线确保脚本、数据、监控都是通的。基线这件事特别重要。没有基线后面所有结果都缺少对照。我曾经因为压测环境里一个下游服务还没联调好导致整个压测结果异常偏慢。如果没有先跑基线排查我可能会花半天时间在应用代码里找一个根本不存在的性能问题。2.3 压测通过标准要先定出来没有通过标准的压测本质上是一次无目的的性能演示。压力测试的通过标准建议包含四个维度响应时间、吞吐量、错误率、资源水位。我常用的模板是这样的维度通过标准响应时间TP99 小于 500msTP999 小于 1000ms吞吐量在目标并发下 QPS 不低于 3000错误率业务错误率小于 0.1%无连接超时资源水位CPU 使用率低于 70%内存无明显增长磁盘 IO 等待低于 20%这个标准不是测试一个人定的最好拉上开发、运维、产品一起对齐。你自己拍一套标准压测没过开发可以说“这个指标没意义”压测过了运维可能说“这个水位在生产根本扛不住”。提前对齐等于给结果安了一个共同认可的尺度。3. 常用压测工具怎么选3.1 开源工具实测感受压测工具本身没有绝对的最好关键看场景。我这些年用过几类大概说下感受。JMeter 是上手门槛最低的图形界面里就能加线程组、配 HTTP 请求、加断言。它的优点是对复杂业务链路支持好可以很方便地串联多个接口、提取 token、做数据关联。缺点也很明显单台压测机在并发非常高时自身可能会成为瓶颈。JMeter 的 GUI 模式在压测时千万别开着监听器界面和结果树会吃掉大量本机资源实际压测应该用非 GUI 模式跑。wrk 是命令行工具脚本用 Lua 写非常轻量。它基于事件驱动单机可以打出很高并发特别适合测纯 HTTP 接口的极限吞吐。但 wrk 不适合做复杂业务链路也很难模拟登录态、多接口关联这类场景。我一般拿它做快速验证改完代码先看接口裸性能不跑完整链路。k6 是我现在用得比较多的。脚本是 JavaScript能直接嵌入 CI/CD 流程结果输出也便于统计。k6 在定义“虚拟用户”和“请求速率”方面很灵活适合团队把压测做成常态化工具。Locust 对熟悉 Python 的人很友好分布式的设计比 JMeter 更干净。缺点是单机并发能力受限压测机多了之后要额外处理 coordinator 和 worker 之间的管理成本。3.2 工具选型对照表如果你不确定该用哪个可以参考这个选择逻辑场景推荐工具理由快速看单个 HTTP 接口的极限性能wrk轻量、启动快、单机并发高复杂业务链路、多接口串联JMeter、Gatling做参数关联和断言更顺手压测流程要自动化接 CI/CDk6脚本进代码仓库版本管理方便团队技术栈是 PythonLocust脚本门槛低分布式方案成熟需要模拟海量用户单机不够JMeter / Locust 分布式模式用多台压测机分摊负载工具选型有个常见误区压测机越多越好。其实多台压测机之间需要同步和汇总管理成本很高。更好的做法是先用单机看能不能压到目标并发单机不行再扩展。如果单机压测时 CPU 已经打满、端口耗尽、网络包重传飙升那说明压测机自己先成了瓶颈这时换再高并发的参数也没意义。4. 完整压测流程实操4.1 搭场景和参数化一个完整的压测场景不只是“启动 N 个线程打接口”。我一般会拆成四步脚本搭建、参数化、断言配置、数据清理。脚本搭建阶段先把业务主流程录出来或手动写出来。如果是 JMeter一个用户完整跑一遍注册、登录、查询、提交四步操作每个接口的请求头、请求体、依赖的上一步返回值都要在脚本里处理好。参数化是让每个虚拟用户拿到的数据不同。最典型的做法是用 CSV Data Set Config 读取准备好的文件。注意“随机”不代表“真实”如果你把 10 万个用户 ID 放在参数文件里随机取但所有请求用的都是同一个 token那测出来的热点分布还是不对的。参数化的原则是和真实用户行为相关的字段要尽量差异化不相关的字段不要制造无谓的随机开销。断言配置很多人会漏掉。压测不能只看 HTTP 状态码是不是 200还要看业务返回码是否正常。有些接口在系统过载时返回的是兜底数据HTTP 是 200但业务上已经完全不可用。这种场景不加业务断言压测结果会严重失真。4.2 分阶段加压策略压测不能上来就万箭齐发。哪怕是测极限值也要给系统一个“逐渐升温”的过程。这样做的原因有两个一是保护业务避免瞬间流量打到生产或预发环境引发故障二是方便观察性能拐点知道并发在哪个范围开始出现劣化。我常用的策略是梯度加压阶段并发数持续时间目的预热503 分钟测试链路连通观察最低水位表现第一梯度1005 分钟拿到稳定基线第二梯度3005 分钟观察资源消耗和响应时间变化第三梯度6005 分钟接近预估峰值检查稳定性极限阶段10003 分钟刻意施压寻找崩溃点恢复阶段降到 1005 分钟观察系统能否自动恢复每轮之间不一定要把并发降回零。如果系统状态没有异常可以连续往上加。但一旦出现错误率飙升、响应时间指数级上涨、CPU 打满就要停止加压先记录当前数据再决定是继续还是重新调整。4.3 跑一次压测的现场记录拿一个模拟场景举例某个订单查询接口部署环境是 4 核 8G 的容器应用使用线程池处理请求数据库连接池上限 150。我按梯度加压跑完之后整理出的数据大概是这样的并发QPSTP99错误率观察到的现象100120045ms0%正常CPU 40%3002800120ms0%正常CPU 65%6004100280ms0.2%慢查询开始增多9003600750ms3.8%吞吐不升反降数据库连接等待变长从 600 并发到 900 并发QPS 没有继续增长反而下降这是一个非常典型的“系统已经进入过载区”的信号。顺着这个结果往下排查看到数据库连接池在 900 并发时处于满负荷等待状态很多请求排队拿不到连接请求时间被拉长进而拖垮了应用层线程池。最终定位到的瓶颈是数据库连接池太小而不是应用代码本身。这个现场记录想说明一件事压测报告不能只写“通过”或“不通过”。每一轮压测的四个指标、当时观察到的现象、初步判断的瓶颈都要写清楚。这些东西在后来的容量评估、架构调整中都是最有价值的依据。5. 结果分析别只看平均响应时间5.1 关键指标怎么读压测结果出来后最大的误区就是盯着平均响应时间。平均值是最容易被骗的指标。10 个请求里 9 个 50ms、1 个 2 秒平均值只有 245ms看起来很好但那个 2 秒的请求背后的用户体验已经崩了。正确的做法是看分位值。TP90、TP99、TP999 分别代表 90%、99%、99.9% 的请求落在多长时间以内。TP99 才是衡量真实体验更可靠的那个数。如果 TP99 涨得比 TP50 快很多说明存在明显的长尾请求要么是某些数据触发了慢路径要么是某台节点已经开始出问题。错误率也要拆着看。HTTP 层面的 5xx、连接超时、业务返回码错误这三种情况代表的意义完全不同。客户端因为连接池耗尽而失败、服务器主动拒绝连接、业务逻辑因为数据问题报错需要分别统计不能混在一起。还有一个很容易被忽略的指标请求重试。很多客户端在超时后会自动重试你看到错误率不高但一次性请求量其实是原来的一倍多。这会放大了服务端的真实压力分析时要留意。5.2 定位瓶颈的排查顺序压测结果不好看时建议按下面这个顺序排查而不是一上来就看代码先看压测客户端自身有没有成为瓶颈。检查压测机 CPU、内存、端口占用、网络带宽。再看网络层。入口带宽有没有打满、负载均衡的转发能力是否正常。看接入层。Nginx 或网关的连接数、worker 进程使用率。看应用层。线程池活跃线程数、队列长度、请求在应用内的耗时拆分。看下游依赖。Redis 慢命令、下游 HTTP 接口耗时、消息队列消费积压。最后看数据层。数据库慢查询、锁等待、连接池使用率。这套顺序的本质是“从近到远”。先从离请求入口最近的环节开始一层层往下拆。很多问题看起来像是数据库慢实际是应用线程池排队时间太长导致数据库查询等待时间被拉高。5.3 用工具辅助定位工具层面我常用的是操作系统命令加应用监控组合。压测过程中在服务端用 top 看 CPU 和内存。重点关注 us用户态 CPU和 waIO 等待。如果 wa 偏高下一步要看磁盘 IO 和数据库。用 vmstat 看上下文切换和 CPU 队列长度如果上下文切换很高可能线程数配置过大。用 netstat 看连接数和 TCP 重传判断是否有端口耗尽或者网络抖动。如果是 Java 应用压测时建议同步抓 JVM 的 GC 日志。很多系统在并发高时表现为停顿、响应变慢其实是 GC 频率和单次耗时飙升。用 jstack 抓线程栈时需要连续抓 3 到 5 次间隔几秒看线程到底阻塞在哪里。单抓一次说明不了问题因为线程状态一直在变。数据库侧的排查比较简单开慢查询日志压测结束后看耗时最高的几条 SQL查 performance_schema 里的锁等待和连接使用率。大多数时候瓶颈从数据库慢查询日志里就能直接看出来。6. 压测中的常见坑6.1 客户端瓶颈吃掉了你的压测结果压测机的性能决定了你能测出多真实的并发。常见的问题是单台压测机端口不够用、文件描述符不够用、线程数开太多导致 CPU 被打满结果服务端可能还没到极限压测机先挂了。我踩过这样的坑用单机 JMeter 跑到 2000 并发时结果始终不稳定后来发现压测机上已经出现了大量的 TIME_WAIT 连接。原因很简单虚拟用户不断创建短连接本地端口被快速占满。处理方式是压测机和被测服务之间开长连接调整本地端口范围必要时使用多台压测机做分布式压测。压测客户端的配置也要提前调。Linux 下需要调整文件描述符上限、关闭 TCP 延迟、开启端口复用。如果不改这些系统参数压测结果会因为客户端限制而失真。6.2 测试数据造成误判测试数据问题在压测里出现的频率极高。我把常见情况列一下数据量太少。查询全部命中缓存结果看起来很快但真实数据量下缓存命中率完全不同。数据太“匀”。真实业务里存在热点数据比如某些爆款商品的访问量远高于其他商品。压测数据如果平均分布测不出热点冲突。重复数据太多。所有请求都操作同一条记录数据库行锁和死锁会异常放大问题。脏数据残留。上一轮压测写了大量数据导致下一轮结果无法对比。所以我在准备压测数据时会先看一眼线上真实的数据量和分布特征然后照着构造一个缩小版。不求完全一致但关键比例要接近。6.3 异常率、TP99 和长尾请求压测报告里最容易出现的问题是只看 QPS 和平均响应时间不看长尾。有一次我压一个查询接口平均响应时间只有 60ms但 TP999 已经到了 2 秒。分析后发现是部分查询会扫描一个大表只是由于它在整体请求里占比小平均值被稀释了。这类问题在真实用户身上非常明显哪怕只有 0.1% 的请求超时对于那个用户来说就是完全不可用。所以每次压测我都会把 TP99 和 TP999 作为比平均值更优先的交付标准。另外异常率不能只看业务异常。连接重置、读超时、写超时、DNS 超时都要单独统计。我在报告里一般会给出两类数显性错误和隐性错误。显性错误是接口直接返回失败隐性错误是客户端超时后重试成功用户没感知但服务端压力增加了一倍。6.4 压测也要有“上线回滚”预案压测本身就是一种对系统的“主动攻击”尤其是压生产环境必须有止损方案。我在压生产之前至少会确认三件事压测脚本有没有最大时长限制监控告警有没有配置有没有办法随时停止压测并通知相关人员。有一次我压一个线上小流量接口由于预估不足40 秒就把下游服务压到了阈值。幸好提前设置了并发上限和时长上限停止之后服务很快恢复。从那之后我养成一个习惯任何压测脚本里都设置一个“硬停止”条件一旦错误率超过设定值或者响应时间达到红线立即终止压测并告警。7. 写在最后一些个人经验7.1 压测是重复劳动值得做成自动化不要把压测当成上线前的一次性动作。项目和系统是持续演进的哪怕只是改了一个第三方依赖版本都可能让性能发生明显变化。我现在的做法是把核心接口的压测脚本纳入 CI/CD 流程每次发版前用低并发跑一个冒烟级别的压测数据超过预设阈值就阻止发布。这样即使没有人专门安排压测任务性能劣化也能在早期被发现。要把压测自动化做起来脚本必须像代码一样管理。压测场景、参数文件、通过标准、执行命令全部放进代码仓库里。后面谁改了接口随手更新脚本就行不能靠某个人的本地文件。7.2 报告要能“吵架”用的压测报告不只是一份数据汇总更是一个沟通工具。开发、运维、产品看的点完全不一样。如果报告只写“系统性能良好建议上线”出了问题没人能拿它当依据。我倾向于在报告里写清楚压测环境是什么、数据形态是什么、每个阶段的压测模型是什么、通过标准是什么、实际结果怎么样、瓶颈定位在哪里、建议优化什么。甚至要把中间过程数据附上方便别人能复现整个结论。这种报告才是能拿去和技术团队讨论、和管理层决策用的。压测这个事表面上是“加压、看数、出报告”实际上是对系统稳定性的一次全面体检。做得越扎实上线的底气就越足。最后分享一个小习惯每次压测完把整个过程里发现的坑记录到一个文档里哪怕当时觉得只是个小问题。下一次遇到类似场景翻一下记录就能避开很多弯路。别高估自己的记忆力也别低估性能问题的隐蔽性。